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

Contents of /lbbs/src/user_list.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.37 - (show annotations)
Tue Nov 18 14:43:35 2025 UTC (3 months, 4 weeks ago) by sysadm
Branch: MAIN
Changes since 1.36: +66 -67 lines
Content type: text/x-csrc
Fix issue caused by address change during SHM remap

1 /* SPDX-License-Identifier: GPL-3.0-or-later */
2 /*
3 * user_list
4 * - data model and basic operations of (online) user list
5 *
6 * Copyright (C) 2004-2025 Leaflet <leaflet@leafok.com>
7 */
8
9 #ifdef HAVE_CONFIG_H
10 #include "config.h"
11 #endif
12
13 #include "common.h"
14 #include "database.h"
15 #include "log.h"
16 #include "trie_dict.h"
17 #include "user_list.h"
18 #include "user_stat.h"
19 #include <errno.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <time.h>
23 #include <sys/ipc.h>
24 #include <sys/mman.h>
25 #include <sys/param.h>
26 #include <sys/sem.h>
27 #include <sys/shm.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 _user_list_constant_t
41 {
42 USER_LIST_TRY_LOCK_WAIT_TIME = 1, // second
43 USER_LIST_TRY_LOCK_TIMES = 10,
44 };
45
46 struct user_list_pool_t
47 {
48 int shmid;
49 int semid;
50 USER_LIST user_list[2];
51 int user_list_index_current;
52 int user_list_index_new;
53 USER_ONLINE_LIST user_online_list[2];
54 int user_online_list_index_current;
55 int user_online_list_index_new;
56 USER_STAT_MAP user_stat_map;
57 int user_login_count;
58 };
59 typedef struct user_list_pool_t USER_LIST_POOL;
60
61 static USER_LIST_POOL *p_user_list_pool = NULL;
62 static TRIE_NODE *p_trie_action_dict = NULL;
63
64 typedef struct user_action_map_t
65 {
66 char name[BBS_current_action_max_len + 1];
67 char title[BBS_current_action_max_len + 1];
68 } USER_ACTION_MAP;
69
70 const USER_ACTION_MAP user_action_map[] =
71 {
72 {"ARTICLE_FAVOR", "浏览收藏"},
73 {"BBS_NET", "站点穿梭"},
74 {"CHICKEN", "电子小鸡"},
75 {"EDIT_ARTICLE", "修改文章"},
76 {"LOGIN", "进入大厅"},
77 {"MENU", "菜单选择"},
78 {"POST_ARTICLE", "撰写文章"},
79 {"REPLY_ARTICLE", "回复文章"},
80 {"USER_LIST", "查花名册"},
81 {"USER_ONLINE", "环顾四周"},
82 {"VIEW_ARTICLE", "阅读文章"},
83 {"VIEW_FILE", "查看文档"},
84 {"WWW", "Web浏览"}};
85
86 const int user_action_map_size = sizeof(user_action_map) / sizeof(USER_ACTION_MAP);
87
88 static int user_list_try_rd_lock(int semid, int wait_sec);
89 static int user_list_try_rw_lock(int semid, int wait_sec);
90 static int user_list_rd_unlock(int semid);
91 static int user_list_rw_unlock(int semid);
92 static int user_list_rd_lock(int semid);
93 static int user_list_rw_lock(int semid);
94
95 static int user_list_load(MYSQL *db, USER_LIST *p_list);
96 static int user_online_list_load(MYSQL *db, USER_ONLINE_LIST *p_online_list);
97 static int user_login_count_load(MYSQL *db);
98
99 static int user_info_index_uid_comp(const void *ptr1, const void *ptr2)
100 {
101 const USER_INFO_INDEX_UID *p1 = ptr1;
102 const USER_INFO_INDEX_UID *p2 = ptr2;
103
104 if (p1->uid < p2->uid)
105 {
106 return -1;
107 }
108 else if (p1->uid > p2->uid)
109 {
110 return 1;
111 }
112 else if (p1->id < p2->id)
113 {
114 return -1;
115 }
116 else if (p1->id > p2->id)
117 {
118 return 1;
119 }
120 return 0;
121 }
122
123 int user_list_load(MYSQL *db, USER_LIST *p_list)
124 {
125 MYSQL_RES *rs = NULL;
126 MYSQL_ROW row;
127 char sql[SQL_BUFFER_LEN];
128 int ret = 0;
129 int i;
130 int j;
131 int32_t last_uid;
132 size_t intro_buf_offset;
133 size_t intro_len;
134
135 if (db == NULL || p_list == NULL)
136 {
137 log_error("NULL pointer error\n");
138 return -1;
139 }
140
141 if (p_list->user_count > 0)
142 {
143 last_uid = p_list->users[p_list->user_count - 1].uid;
144 }
145 else
146 {
147 last_uid = -1;
148 }
149
150 snprintf(sql, sizeof(sql),
151 "SELECT user_list.UID AS UID, username, nickname, gender, gender_pub, life, exp, visit_count, "
152 "UNIX_TIMESTAMP(signup_dt), UNIX_TIMESTAMP(last_login_dt), UNIX_TIMESTAMP(last_logout_dt), "
153 "UNIX_TIMESTAMP(birthday), `introduction` "
154 "FROM user_list INNER JOIN user_pubinfo ON user_list.UID = user_pubinfo.UID "
155 "INNER JOIN user_reginfo ON user_list.UID = user_reginfo.UID "
156 "WHERE enable ORDER BY username");
157
158 if (mysql_query(db, sql) != 0)
159 {
160 log_error("Query user info error: %s\n", mysql_error(db));
161 ret = -1;
162 goto cleanup;
163 }
164
165 if ((rs = mysql_use_result(db)) == NULL)
166 {
167 log_error("Get user info data failed\n");
168 ret = -1;
169 goto cleanup;
170 }
171
172 intro_buf_offset = 0;
173 i = 0;
174 while ((row = mysql_fetch_row(rs)))
175 {
176 // record
177 p_list->users[i].id = i;
178 p_list->users[i].uid = atoi(row[0]);
179 strncpy(p_list->users[i].username, row[1], sizeof(p_list->users[i].username) - 1);
180 p_list->users[i].username[sizeof(p_list->users[i].username) - 1] = '\0';
181 strncpy(p_list->users[i].nickname, row[2], sizeof(p_list->users[i].nickname) - 1);
182 p_list->users[i].nickname[sizeof(p_list->users[i].nickname) - 1] = '\0';
183 p_list->users[i].gender = row[3][0];
184 p_list->users[i].gender_pub = (int8_t)(row[4] == NULL ? 0 : atoi(row[4]));
185 p_list->users[i].life = (row[5] == NULL ? 0 : atoi(row[5]));
186 p_list->users[i].exp = (row[6] == NULL ? 0 : atoi(row[6]));
187 p_list->users[i].visit_count = (row[7] == NULL ? 0 : atoi(row[7]));
188 p_list->users[i].signup_dt = (row[8] == NULL ? 0 : atol(row[8]));
189 p_list->users[i].last_login_dt = (row[9] == NULL ? 0 : atol(row[9]));
190 p_list->users[i].last_logout_dt = (row[10] == NULL ? 0 : atol(row[10]));
191 p_list->users[i].birthday = (row[11] == NULL ? 0 : atol(row[11]));
192 intro_len = strlen((row[12] == NULL ? "" : row[12]));
193 if (intro_len >= sizeof(p_list->user_intro_buf) - 1 - intro_buf_offset)
194 {
195 log_error("OOM for user introduction: len=%d, i=%d\n", intro_len, i);
196 break;
197 }
198 memcpy(p_list->user_intro_buf + intro_buf_offset,
199 (row[12] == NULL ? "" : row[12]),
200 intro_len + 1);
201 p_list->users[i].intro = p_list->user_intro_buf + intro_buf_offset;
202 intro_buf_offset += (intro_len + 1);
203
204 i++;
205 if (i >= BBS_max_user_count)
206 {
207 log_error("Too many users, exceed limit %d\n", BBS_max_user_count);
208 break;
209 }
210 }
211 mysql_free_result(rs);
212 rs = NULL;
213
214 if (i != p_list->user_count || p_list->users[i - 1].uid != last_uid) // Count of users changed
215 {
216 // Rebuild index
217 for (j = 0; j < i; j++)
218 {
219 p_list->index_uid[j].uid = p_list->users[j].uid;
220 p_list->index_uid[j].id = j;
221 }
222
223 qsort(p_list->index_uid, (size_t)i, sizeof(USER_INFO_INDEX_UID), user_info_index_uid_comp);
224
225 #ifdef _DEBUG
226 log_error("Rebuild index of %d users, last_uid=%d\n", i, p_list->users[i - 1].uid);
227 #endif
228 }
229
230 p_list->user_count = i;
231
232 #ifdef _DEBUG
233 log_error("Loaded %d users\n", p_list->user_count);
234 #endif
235
236 cleanup:
237 mysql_free_result(rs);
238
239 return ret;
240 }
241
242 int user_online_list_load(MYSQL *db, USER_ONLINE_LIST *p_online_list)
243 {
244 MYSQL_RES *rs = NULL;
245 MYSQL_ROW row;
246 char sql[SQL_BUFFER_LEN];
247 int ret = 0;
248 int i;
249 int j;
250 int user_cnt;
251 int guest_cnt;
252
253 if (db == NULL || p_online_list == NULL)
254 {
255 log_error("NULL pointer error\n");
256 return -1;
257 }
258
259 snprintf(sql, sizeof(sql),
260 "SELECT SID, UID, ip, current_action, UNIX_TIMESTAMP(login_tm), "
261 "UNIX_TIMESTAMP(last_tm) FROM user_online "
262 "WHERE last_tm >= SUBDATE(NOW(), INTERVAL %d SECOND) "
263 "ORDER BY last_tm DESC",
264 BBS_user_off_line);
265
266 if (mysql_query(db, sql) != 0)
267 {
268 log_error("Query user online error: %s\n", mysql_error(db));
269 ret = -1;
270 goto cleanup;
271 }
272
273 if ((rs = mysql_use_result(db)) == NULL)
274 {
275 log_error("Get user online data failed\n");
276 ret = -1;
277 goto cleanup;
278 }
279
280 i = 0;
281 user_cnt = 0;
282 guest_cnt = 0;
283 while ((row = mysql_fetch_row(rs)))
284 {
285 if (atoi(row[1]) == 0) // guest
286 {
287 guest_cnt++;
288 continue;
289 }
290 else
291 {
292 user_cnt++;
293 }
294
295 p_online_list->users[i].id = i;
296 strncpy(p_online_list->users[i].session_id, row[0], sizeof(p_online_list->users[i].session_id) - 1);
297 p_online_list->users[i].session_id[sizeof(p_online_list->users[i].session_id) - 1] = '\0';
298
299 if ((ret = query_user_info_by_uid(atoi(row[1]), &(p_online_list->users[i].user_info), NULL, 0)) <= 0)
300 {
301 log_error("query_user_info_by_uid(%d) error\n", atoi(row[1]));
302 continue;
303 }
304
305 strncpy(p_online_list->users[i].ip, row[2], sizeof(p_online_list->users[i].ip) - 1);
306 p_online_list->users[i].ip[sizeof(p_online_list->users[i].ip) - 1] = '\0';
307
308 strncpy(p_online_list->users[i].current_action, row[3], sizeof(p_online_list->users[i].current_action) - 1);
309 p_online_list->users[i].current_action[sizeof(p_online_list->users[i].current_action) - 1] = '\0';
310 p_online_list->users[i].current_action_title = NULL;
311 if (p_online_list->users[i].current_action[0] == '\0')
312 {
313 p_online_list->users[i].current_action_title = "";
314 }
315 else if (trie_dict_get(p_trie_action_dict, p_online_list->users[i].current_action, (int64_t *)(&(p_online_list->users[i].current_action_title))) < 0)
316 {
317 log_error("trie_dict_get(p_trie_action_dict, %s) error on session_id=%s\n",
318 p_online_list->users[i].current_action, p_online_list->users[i].session_id);
319 continue;
320 }
321
322 p_online_list->users[i].login_tm = (row[4] == NULL ? 0 : atol(row[4]));
323 p_online_list->users[i].last_tm = (row[5] == NULL ? 0 : atol(row[5]));
324
325 i++;
326 if (i >= BBS_max_user_online_count)
327 {
328 log_error("Too many online users, exceed limit %d\n", BBS_max_user_online_count);
329 break;
330 }
331 }
332 mysql_free_result(rs);
333 rs = NULL;
334
335 if (user_cnt > 0)
336 {
337 // Rebuild index
338 for (j = 0; j < user_cnt; j++)
339 {
340 p_online_list->index_uid[j].uid = p_online_list->users[j].user_info.uid;
341 p_online_list->index_uid[j].id = j;
342 }
343
344 qsort(p_online_list->index_uid, (size_t)user_cnt, sizeof(USER_INFO_INDEX_UID), user_info_index_uid_comp);
345 }
346
347 p_online_list->user_count = user_cnt;
348 p_online_list->guest_count = guest_cnt;
349
350 cleanup:
351 mysql_free_result(rs);
352
353 return ret;
354 }
355
356 int user_login_count_load(MYSQL *db)
357 {
358 MYSQL_RES *rs = NULL;
359 MYSQL_ROW row;
360 char sql[SQL_BUFFER_LEN];
361
362 if (db == NULL)
363 {
364 log_error("NULL pointer error\n");
365 return -1;
366 }
367
368 snprintf(sql, sizeof(sql),
369 "SELECT ID FROM user_login_log ORDER BY ID DESC LIMIT 1");
370 if (mysql_query(db, sql) != 0)
371 {
372 log_error("Query user_login_log error: %s\n", mysql_error(db));
373 return -2;
374 }
375 if ((rs = mysql_store_result(db)) == NULL)
376 {
377 log_error("Get user_login_log data failed\n");
378 return -2;
379 }
380 if ((row = mysql_fetch_row(rs)))
381 {
382 p_user_list_pool->user_login_count = atoi(row[0]);
383 }
384 mysql_free_result(rs);
385
386 return 0;
387 }
388
389 int user_list_pool_init(const char *filename)
390 {
391 int shmid;
392 int semid;
393 int proj_id;
394 key_t key;
395 size_t size;
396 void *p_shm;
397 union semun arg;
398 int i;
399
400 if (p_user_list_pool != NULL || p_trie_action_dict != NULL)
401 {
402 log_error("p_user_list_pool already initialized\n");
403 return -1;
404 }
405
406 p_trie_action_dict = trie_dict_create();
407 if (p_trie_action_dict == NULL)
408 {
409 log_error("trie_dict_create() error\n");
410 return -1;
411 }
412
413 for (i = 0; i < user_action_map_size; i++)
414 {
415 if (trie_dict_set(p_trie_action_dict, user_action_map[i].name, (int64_t)(user_action_map[i].title)) < 0)
416 {
417 log_error("trie_dict_set(p_trie_action_dict, %s) error\n", user_action_map[i].name);
418 }
419 }
420
421 // Allocate shared memory
422 proj_id = (int)(time(NULL) % getpid());
423 key = ftok(filename, proj_id);
424 if (key == -1)
425 {
426 log_error("ftok(%s %d) error (%d)\n", filename, proj_id, errno);
427 return -2;
428 }
429
430 size = sizeof(USER_LIST_POOL);
431 shmid = shmget(key, size, IPC_CREAT | IPC_EXCL | 0600);
432 if (shmid == -1)
433 {
434 log_error("shmget(size = %d) error (%d)\n", size, errno);
435 return -3;
436 }
437 p_shm = shmat(shmid, NULL, 0);
438 if (p_shm == (void *)-1)
439 {
440 log_error("shmat(shmid=%d) error (%d)\n", shmid, errno);
441 return -3;
442 }
443
444 p_user_list_pool = p_shm;
445 p_user_list_pool->shmid = shmid;
446
447 // Allocate semaphore as user list pool lock
448 size = 2; // r_sem and w_sem
449 semid = semget(key, (int)size, IPC_CREAT | IPC_EXCL | 0600);
450 if (semid == -1)
451 {
452 log_error("semget(user_list_pool_sem, size = %d) error (%d)\n", size, errno);
453 return -3;
454 }
455
456 // Initialize sem value to 0
457 arg.val = 0;
458 for (i = 0; i < size; i++)
459 {
460 if (semctl(semid, i, SETVAL, arg) == -1)
461 {
462 log_error("semctl(user_list_pool_sem, SETVAL) error (%d)\n", errno);
463 return -3;
464 }
465 }
466
467 p_user_list_pool->semid = semid;
468
469 // Set user counts to 0
470 p_user_list_pool->user_list[0].user_count = 0;
471 p_user_list_pool->user_list[1].user_count = 0;
472
473 p_user_list_pool->user_list_index_current = 0;
474 p_user_list_pool->user_list_index_new = 1;
475
476 p_user_list_pool->user_online_list_index_current = 0;
477 p_user_list_pool->user_online_list_index_new = 1;
478
479 user_stat_map_init(&(p_user_list_pool->user_stat_map));
480
481 return 0;
482 }
483
484 void user_list_pool_cleanup(void)
485 {
486 int shmid;
487
488 if (p_user_list_pool == NULL)
489 {
490 return;
491 }
492
493 shmid = p_user_list_pool->shmid;
494
495 if (semctl(p_user_list_pool->semid, 0, IPC_RMID) == -1)
496 {
497 log_error("semctl(semid = %d, IPC_RMID) error (%d)\n", p_user_list_pool->semid, errno);
498 }
499
500 if (shmdt(p_user_list_pool) == -1)
501 {
502 log_error("shmdt(shmid = %d) error (%d)\n", shmid, errno);
503 }
504
505 if (shmid != 0 && shmctl(shmid, IPC_RMID, NULL) == -1)
506 {
507 log_error("shmctl(shmid = %d, IPC_RMID) error (%d)\n", shmid, errno);
508 }
509
510 p_user_list_pool = NULL;
511
512 if (p_trie_action_dict != NULL)
513 {
514 trie_dict_destroy(p_trie_action_dict);
515
516 p_trie_action_dict = NULL;
517 }
518 }
519
520 int set_user_list_pool_shm_readonly(void)
521 {
522 int shmid;
523 void *p_shm;
524
525 if (p_user_list_pool == NULL)
526 {
527 log_error("p_user_list_pool not initialized\n");
528 return -1;
529 }
530
531 shmid = p_user_list_pool->shmid;
532
533 // Remap shared memory in read-only mode
534 #if defined(__CYGWIN__)
535 if (shmdt(p_user_list_pool) == -1)
536 {
537 log_error("shmdt(user_list_pool) error (%d)\n", errno);
538 return -1;
539 }
540 p_shm = shmat(shmid, p_user_list_pool, SHM_RDONLY);
541 #else
542 p_shm = shmat(shmid, p_user_list_pool, SHM_RDONLY | SHM_REMAP);
543 #endif
544 if (p_shm == (void *)-1)
545 {
546 log_error("shmat(user_list_pool shmid = %d) error (%d)\n", shmid, errno);
547 return -3;
548 }
549
550 p_user_list_pool = p_shm;
551
552 return 0;
553 }
554
555 int detach_user_list_pool_shm(void)
556 {
557 if (p_user_list_pool != NULL && shmdt(p_user_list_pool) == -1)
558 {
559 log_error("shmdt(user_list_pool) error (%d)\n", errno);
560 return -1;
561 }
562
563 p_user_list_pool = NULL;
564
565 return 0;
566 }
567
568 int user_list_pool_reload(int online_user)
569 {
570 MYSQL *db = NULL;
571 int tmp;
572 int ret = 0;
573
574 if (p_user_list_pool == NULL)
575 {
576 log_error("p_user_list_pool not initialized\n");
577 return -1;
578 }
579
580 db = db_open();
581 if (db == NULL)
582 {
583 log_error("db_open() error: %s\n", mysql_error(db));
584 return -1;
585 }
586
587 if (online_user)
588 {
589 if (user_online_list_load(db, &(p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_new])) < 0)
590 {
591 log_error("user_online_list_load() error\n");
592 ret = -2;
593 goto cleanup;
594 }
595
596 if (user_login_count_load(db) < 0)
597 {
598 log_error("user_login_count_load() error\n");
599 ret = -2;
600 goto cleanup;
601 }
602 }
603 else
604 {
605 if (user_list_load(db, &(p_user_list_pool->user_list[p_user_list_pool->user_list_index_new])) < 0)
606 {
607 log_error("user_list_load() error\n");
608 ret = -2;
609 goto cleanup;
610 }
611 }
612
613 mysql_close(db);
614 db = NULL;
615
616 if (user_list_rw_lock(p_user_list_pool->semid) < 0)
617 {
618 log_error("user_list_rw_lock() error\n");
619 ret = -3;
620 goto cleanup;
621 }
622
623 if (online_user)
624 {
625 // Swap p_online_current and p_online_new
626 tmp = p_user_list_pool->user_online_list_index_current;
627 p_user_list_pool->user_online_list_index_current = p_user_list_pool->user_online_list_index_new;
628 p_user_list_pool->user_online_list_index_new = tmp;
629 }
630 else
631 {
632 // Swap index_current and index_new
633 tmp = p_user_list_pool->user_list_index_current;
634 p_user_list_pool->user_list_index_current = p_user_list_pool->user_list_index_new;
635 p_user_list_pool->user_list_index_new = tmp;
636 }
637
638 if (user_list_rw_unlock(p_user_list_pool->semid) < 0)
639 {
640 log_error("user_list_rw_unlock() error\n");
641 ret = -3;
642 goto cleanup;
643 }
644
645 cleanup:
646 mysql_close(db);
647
648 return ret;
649 }
650
651 int user_list_try_rd_lock(int semid, int wait_sec)
652 {
653 struct sembuf sops[2];
654 #if !defined(__CYGWIN__)
655 struct timespec timeout;
656 #endif
657 int ret;
658
659 sops[0].sem_num = 1; // w_sem
660 sops[0].sem_op = 0; // wait until unlocked
661 sops[0].sem_flg = 0;
662
663 sops[1].sem_num = 0; // r_sem
664 sops[1].sem_op = 1; // lock
665 sops[1].sem_flg = SEM_UNDO; // undo on terminate
666
667 #if defined(__CYGWIN__)
668 ret = semop(semid, sops, 2);
669 #else
670 timeout.tv_sec = wait_sec;
671 timeout.tv_nsec = 0;
672
673 ret = semtimedop(semid, sops, 2, &timeout);
674 #endif
675 if (ret == -1 && errno != EAGAIN && errno != EINTR)
676 {
677 log_error("semop(lock read) error %d\n", errno);
678 }
679
680 return ret;
681 }
682
683 int user_list_try_rw_lock(int semid, int wait_sec)
684 {
685 struct sembuf sops[3];
686 #if !defined(__CYGWIN__)
687 struct timespec timeout;
688 #endif
689 int ret;
690
691 sops[0].sem_num = 1; // w_sem
692 sops[0].sem_op = 0; // wait until unlocked
693 sops[0].sem_flg = 0;
694
695 sops[1].sem_num = 1; // w_sem
696 sops[1].sem_op = 1; // lock
697 sops[1].sem_flg = SEM_UNDO; // undo on terminate
698
699 sops[2].sem_num = 0; // r_sem
700 sops[2].sem_op = 0; // wait until unlocked
701 sops[2].sem_flg = 0;
702
703 #if defined(__CYGWIN__)
704 ret = semop(semid, sops, 3);
705 #else
706 timeout.tv_sec = wait_sec;
707 timeout.tv_nsec = 0;
708
709 ret = semtimedop(semid, sops, 3, &timeout);
710 #endif
711 if (ret == -1 && errno != EAGAIN && errno != EINTR)
712 {
713 log_error("semop(lock write) error %d\n", errno);
714 }
715
716 return ret;
717 }
718
719 int user_list_rd_unlock(int semid)
720 {
721 struct sembuf sops[2];
722 int ret;
723
724 sops[0].sem_num = 0; // r_sem
725 sops[0].sem_op = -1; // unlock
726 sops[0].sem_flg = IPC_NOWAIT | SEM_UNDO; // no wait
727
728 ret = semop(semid, sops, 1);
729 if (ret == -1 && errno != EAGAIN && errno != EINTR)
730 {
731 log_error("semop(unlock read) error %d\n", errno);
732 }
733
734 return ret;
735 }
736
737 int user_list_rw_unlock(int semid)
738 {
739 struct sembuf sops[1];
740 int ret;
741
742 sops[0].sem_num = 1; // w_sem
743 sops[0].sem_op = -1; // unlock
744 sops[0].sem_flg = IPC_NOWAIT | SEM_UNDO; // no wait
745
746 ret = semop(semid, sops, 1);
747 if (ret == -1 && errno != EAGAIN && errno != EINTR)
748 {
749 log_error("semop(unlock write) error %d\n", errno);
750 }
751
752 return ret;
753 }
754
755 int user_list_rd_lock(int semid)
756 {
757 int timer = 0;
758 int ret = -1;
759
760 while (!SYS_server_exit)
761 {
762 ret = user_list_try_rd_lock(semid, USER_LIST_TRY_LOCK_WAIT_TIME);
763 if (ret == 0) // success
764 {
765 break;
766 }
767 else if (errno == EAGAIN || errno == EINTR) // retry
768 {
769 timer++;
770 if (timer % USER_LIST_TRY_LOCK_TIMES == 0)
771 {
772 log_error("user_list_try_rd_lock() tried %d times\n", timer);
773 }
774 }
775 else // failed
776 {
777 log_error("user_list_try_rd_lock() failed\n");
778 break;
779 }
780 }
781
782 return ret;
783 }
784
785 int user_list_rw_lock(int semid)
786 {
787 int timer = 0;
788 int ret = -1;
789
790 while (!SYS_server_exit)
791 {
792 ret = user_list_try_rw_lock(semid, USER_LIST_TRY_LOCK_WAIT_TIME);
793 if (ret == 0) // success
794 {
795 break;
796 }
797 else if (errno == EAGAIN || errno == EINTR) // retry
798 {
799 timer++;
800 if (timer % USER_LIST_TRY_LOCK_TIMES == 0)
801 {
802 log_error("user_list_try_rw_lock() tried %d times\n", timer);
803 }
804 }
805 else // failed
806 {
807 log_error("user_list_try_rw_lock() failed\n");
808 break;
809 }
810 }
811
812 return ret;
813 }
814
815 int query_user_list(int page_id, USER_INFO *p_users, int *p_user_count, int *p_page_count)
816 {
817 int ret = 0;
818
819 if (p_users == NULL || p_user_count == NULL || p_page_count == NULL)
820 {
821 log_error("NULL pointer error\n");
822 return -1;
823 }
824
825 *p_user_count = 0;
826 *p_page_count = 0;
827
828 // acquire lock of user list
829 if (user_list_rd_lock(p_user_list_pool->semid) < 0)
830 {
831 log_error("user_list_rd_lock() error\n");
832 return -2;
833 }
834
835 if (p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].user_count == 0)
836 {
837 // empty list
838 ret = 0;
839 goto cleanup;
840 }
841
842 *p_page_count = (p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].user_count + BBS_user_limit_per_page - 1) /
843 BBS_user_limit_per_page;
844
845 if (page_id < 0 || page_id >= *p_page_count)
846 {
847 log_error("Invalid page_id = %d, not in range [0, %d)\n", page_id, *p_page_count);
848 ret = -3;
849 goto cleanup;
850 }
851
852 *p_user_count = MIN(BBS_user_limit_per_page,
853 p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].user_count -
854 page_id * BBS_user_limit_per_page);
855
856 memcpy(p_users,
857 p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].users + page_id * BBS_user_limit_per_page,
858 sizeof(USER_INFO) * (size_t)(*p_user_count));
859
860 cleanup:
861 // release lock of user list
862 if (user_list_rd_unlock(p_user_list_pool->semid) < 0)
863 {
864 log_error("user_list_rd_unlock() error\n");
865 ret = -1;
866 }
867
868 return ret;
869 }
870
871 int query_user_online_list(int page_id, USER_ONLINE_INFO *p_online_users, int *p_user_count, int *p_page_count)
872 {
873 int ret = 0;
874
875 if (p_online_users == NULL || p_user_count == NULL || p_page_count == NULL)
876 {
877 log_error("NULL pointer error\n");
878 return -1;
879 }
880
881 *p_user_count = 0;
882 *p_page_count = 0;
883
884 // acquire lock of user list
885 if (user_list_rd_lock(p_user_list_pool->semid) < 0)
886 {
887 log_error("user_list_rd_lock() error\n");
888 return -2;
889 }
890
891 if (p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].user_count == 0)
892 {
893 // empty list
894 ret = 0;
895 goto cleanup;
896 }
897
898 *p_page_count = (p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].user_count + BBS_user_limit_per_page - 1) / BBS_user_limit_per_page;
899
900 if (page_id < 0 || page_id >= *p_page_count)
901 {
902 log_error("Invalid page_id = %d, not in range [0, %d)\n", page_id, *p_page_count);
903 ret = -3;
904 goto cleanup;
905 }
906
907 *p_user_count = MIN(BBS_user_limit_per_page,
908 p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].user_count -
909 page_id * BBS_user_limit_per_page);
910
911 memcpy(p_online_users,
912 p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].users + page_id * BBS_user_limit_per_page,
913 sizeof(USER_ONLINE_INFO) * (size_t)(*p_user_count));
914
915 cleanup:
916 // release lock of user list
917 if (user_list_rd_unlock(p_user_list_pool->semid) < 0)
918 {
919 log_error("user_list_rd_unlock() error\n");
920 ret = -1;
921 }
922
923 return ret;
924 }
925
926 int get_user_list_count(int *p_user_cnt)
927 {
928 if (p_user_cnt == NULL)
929 {
930 log_error("NULL pointer error\n");
931 return -1;
932 }
933
934 // acquire lock of user list
935 if (user_list_rd_lock(p_user_list_pool->semid) < 0)
936 {
937 log_error("user_list_rd_lock() error\n");
938 return -2;
939 }
940
941 *p_user_cnt = p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].user_count;
942
943 // release lock of user list
944 if (user_list_rd_unlock(p_user_list_pool->semid) < 0)
945 {
946 log_error("user_list_rd_unlock() error\n");
947 return -2;
948 }
949
950 return 0;
951 }
952
953 int get_user_online_list_count(int *p_user_cnt, int *p_guest_cnt)
954 {
955 if (p_user_cnt == NULL || p_guest_cnt == NULL)
956 {
957 log_error("NULL pointer error\n");
958 return -1;
959 }
960
961 // acquire lock of user list
962 if (user_list_rd_lock(p_user_list_pool->semid) < 0)
963 {
964 log_error("user_list_rd_lock() error\n");
965 return -2;
966 }
967
968 *p_user_cnt = p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].user_count;
969 *p_guest_cnt = p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].guest_count;
970
971 // release lock of user list
972 if (user_list_rd_unlock(p_user_list_pool->semid) < 0)
973 {
974 log_error("user_list_rd_unlock() error\n");
975 return -2;
976 }
977
978 return 0;
979 }
980
981 int get_user_login_count(int *p_login_cnt)
982 {
983 if (p_login_cnt == NULL)
984 {
985 log_error("NULL pointer error\n");
986 return -1;
987 }
988
989 *p_login_cnt = p_user_list_pool->user_login_count;
990
991 return 0;
992 }
993
994 int query_user_info(int32_t id, USER_INFO *p_user)
995 {
996 int ret = 0;
997
998 if (p_user == NULL)
999 {
1000 log_error("NULL pointer error\n");
1001 return -1;
1002 }
1003
1004 // acquire lock of user list
1005 if (user_list_rd_lock(p_user_list_pool->semid) < 0)
1006 {
1007 log_error("user_list_rd_lock() error\n");
1008 return -2;
1009 }
1010
1011 if (id >= 0 && id < p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].user_count) // Found
1012 {
1013 *p_user = p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].users[id];
1014 ret = 1;
1015 }
1016
1017 // release lock of user list
1018 if (user_list_rd_unlock(p_user_list_pool->semid) < 0)
1019 {
1020 log_error("user_list_rd_unlock() error\n");
1021 ret = -1;
1022 }
1023
1024 return ret;
1025 }
1026
1027 int query_user_info_by_uid(int32_t uid, USER_INFO *p_user, char *p_intro_buf, size_t intro_buf_len)
1028 {
1029 int left;
1030 int right;
1031 int mid;
1032 int32_t id;
1033 int ret = 0;
1034
1035 if (p_user == NULL)
1036 {
1037 log_error("NULL pointer error\n");
1038 return -1;
1039 }
1040
1041 // acquire lock of user list
1042 if (user_list_rd_lock(p_user_list_pool->semid) < 0)
1043 {
1044 log_error("user_list_rd_lock() error\n");
1045 return -2;
1046 }
1047
1048 left = 0;
1049 right = p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].user_count - 1;
1050
1051 while (left < right)
1052 {
1053 mid = (left + right) / 2;
1054 if (uid < p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].index_uid[mid].uid)
1055 {
1056 right = mid - 1;
1057 }
1058 else if (uid > p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].index_uid[mid].uid)
1059 {
1060 left = mid + 1;
1061 }
1062 else // if (uid == p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].index_uid[mid].uid)
1063 {
1064 left = mid;
1065 break;
1066 }
1067 }
1068
1069 if (uid == p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].index_uid[left].uid) // Found
1070 {
1071 id = p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].index_uid[left].id;
1072 *p_user = p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].users[id];
1073 ret = 1;
1074
1075 if (p_intro_buf != NULL)
1076 {
1077 strncpy(p_intro_buf, p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].users[id].intro, intro_buf_len - 1);
1078 p_intro_buf[intro_buf_len - 1] = '\0';
1079 p_user->intro = p_intro_buf;
1080 }
1081 }
1082
1083 // release lock of user list
1084 if (user_list_rd_unlock(p_user_list_pool->semid) < 0)
1085 {
1086 log_error("user_list_rd_unlock() error\n");
1087 ret = -1;
1088 }
1089
1090 return ret;
1091 }
1092
1093 int query_user_info_by_username(const char *username_prefix, int max_user_cnt,
1094 int32_t uid_list[], char username_list[][BBS_username_max_len + 1])
1095 {
1096 int left;
1097 int right;
1098 int mid;
1099 int left_save;
1100 int ret = 0;
1101 size_t prefix_len;
1102 int comp;
1103 int i;
1104
1105 if (username_prefix == NULL || uid_list == NULL || username_list == NULL)
1106 {
1107 log_error("NULL pointer error\n");
1108 return -1;
1109 }
1110
1111 prefix_len = strlen(username_prefix);
1112
1113 // acquire lock of user list
1114 if (user_list_rd_lock(p_user_list_pool->semid) < 0)
1115 {
1116 log_error("user_list_rd_lock() error\n");
1117 return -2;
1118 }
1119
1120 left = 0;
1121 right = p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].user_count - 1;
1122
1123 while (left < right)
1124 {
1125 mid = (left + right) / 2;
1126 comp = strncasecmp(username_prefix, p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].users[mid].username, prefix_len);
1127 if (comp < 0)
1128 {
1129 right = mid - 1;
1130 }
1131 else if (comp > 0)
1132 {
1133 left = mid + 1;
1134 }
1135 else // if (comp == 0)
1136 {
1137 left = mid;
1138 break;
1139 }
1140 }
1141
1142 if (strncasecmp(username_prefix, p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].users[left].username, prefix_len) == 0) // Found
1143 {
1144 #ifdef _DEBUG
1145 log_error("Debug: match found, pos=%d\n", left);
1146 #endif
1147
1148 left_save = left;
1149 right = left;
1150 left = 0;
1151
1152 while (left < right)
1153 {
1154 mid = (left + right) / 2;
1155 comp = strncasecmp(username_prefix, p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].users[mid].username, prefix_len);
1156 if (comp > 0)
1157 {
1158 left = mid + 1;
1159 }
1160 else if (comp == 0)
1161 {
1162 right = mid;
1163 }
1164 else // if (comp < 0)
1165 {
1166 log_error("Bug: left=%d right=%d mid=%d");
1167 ret = -2;
1168 goto cleanup;
1169 }
1170 }
1171
1172 #ifdef _DEBUG
1173 log_error("Debug: first match found, pos=%d\n", right);
1174 #endif
1175
1176 left = left_save;
1177 left_save = right;
1178 right = p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].user_count - 1;
1179
1180 while (left < right)
1181 {
1182 mid = (left + right) / 2 + (left + right) % 2;
1183 comp = strncasecmp(username_prefix, p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].users[mid].username, prefix_len);
1184 if (comp < 0)
1185 {
1186 right = mid - 1;
1187 }
1188 else if (comp == 0)
1189 {
1190 left = mid;
1191 }
1192 else // if (comp > 0)
1193 {
1194 log_error("Bug: left=%d right=%d mid=%d");
1195 ret = -2;
1196 goto cleanup;
1197 }
1198 }
1199
1200 #ifdef _DEBUG
1201 log_error("Debug: last match found, pos=%d\n", left);
1202 #endif
1203
1204 right = left;
1205 left = left_save;
1206
1207 for (i = 0; i < max_user_cnt && left + i <= right; i++)
1208 {
1209 uid_list[i] = p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].users[left + i].uid;
1210 strncpy(username_list[i], p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].users[left + i].username, sizeof(username_list[i]) - 1);
1211 username_list[i][sizeof(username_list[i]) - 1] = '\0';
1212 }
1213 ret = i;
1214 }
1215
1216 cleanup:
1217 // release lock of user list
1218 if (user_list_rd_unlock(p_user_list_pool->semid) < 0)
1219 {
1220 log_error("user_list_rd_unlock() error\n");
1221 ret = -1;
1222 }
1223
1224 return ret;
1225 }
1226
1227 int query_user_online_info(int32_t id, USER_ONLINE_INFO *p_user)
1228 {
1229 int ret = 0;
1230
1231 if (p_user == NULL)
1232 {
1233 log_error("NULL pointer error\n");
1234 return -1;
1235 }
1236
1237 // acquire lock of user list
1238 if (user_list_rd_lock(p_user_list_pool->semid) < 0)
1239 {
1240 log_error("user_list_rd_lock() error\n");
1241 return -2;
1242 }
1243
1244 if (id >= 0 && id < p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].user_count) // Found
1245 {
1246 *p_user = p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].users[id];
1247 ret = 1;
1248 }
1249
1250 // release lock of user list
1251 if (user_list_rd_unlock(p_user_list_pool->semid) < 0)
1252 {
1253 log_error("user_list_rd_unlock() error\n");
1254 ret = -1;
1255 }
1256
1257 return ret;
1258 }
1259
1260 int query_user_online_info_by_uid(int32_t uid, USER_ONLINE_INFO *p_users, int *p_user_cnt, int start_id)
1261 {
1262 int left;
1263 int right;
1264 int mid;
1265 int32_t id;
1266 int ret = 0;
1267 int i;
1268 int user_cnt;
1269
1270 if (p_users == NULL || p_user_cnt == NULL)
1271 {
1272 log_error("NULL pointer error\n");
1273 return -1;
1274 }
1275
1276 user_cnt = *p_user_cnt;
1277 *p_user_cnt = 0;
1278
1279 // acquire lock of user list
1280 if (user_list_rd_lock(p_user_list_pool->semid) < 0)
1281 {
1282 log_error("user_list_rd_lock() error\n");
1283 return -2;
1284 }
1285
1286 left = start_id;
1287 right = p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].user_count - 1;
1288
1289 while (left < right)
1290 {
1291 mid = (left + right) / 2;
1292 if (uid < p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].index_uid[mid].uid)
1293 {
1294 right = mid - 1;
1295 }
1296 else if (uid > p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].index_uid[mid].uid)
1297 {
1298 left = mid + 1;
1299 }
1300 else // if (uid == p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].index_uid[mid].uid)
1301 {
1302 left = mid;
1303 break;
1304 }
1305 }
1306
1307 if (uid == p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].index_uid[left].uid)
1308 {
1309 right = left;
1310 left = start_id;
1311
1312 while (left < right)
1313 {
1314 mid = (left + right) / 2;
1315 if (uid - 1 < p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].index_uid[mid].uid)
1316 {
1317 right = mid;
1318 }
1319 else // if (uid - 1 >= p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].index_uid[mid].uid)
1320 {
1321 left = mid + 1;
1322 }
1323 }
1324
1325 for (i = 0;
1326 left < p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].user_count && i < user_cnt &&
1327 uid == p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].index_uid[left].uid;
1328 left++, i++)
1329 {
1330 id = p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].index_uid[left].id;
1331 p_users[i] = p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].users[id];
1332 }
1333
1334 if (i > 0)
1335 {
1336 *p_user_cnt = i;
1337 ret = 1;
1338 }
1339 }
1340
1341 // release lock of user list
1342 if (user_list_rd_unlock(p_user_list_pool->semid) < 0)
1343 {
1344 log_error("user_list_rd_unlock() error\n");
1345 ret = -1;
1346 }
1347
1348 return ret;
1349 }
1350
1351 int get_user_id_list(int32_t *p_uid_list, int *p_user_cnt, int start_uid)
1352 {
1353 int left;
1354 int right;
1355 int mid;
1356 int ret = 0;
1357 int i;
1358
1359 if (p_uid_list == NULL || p_user_cnt == NULL)
1360 {
1361 log_error("NULL pointer error\n");
1362 return -1;
1363 }
1364
1365 // acquire lock of user list
1366 if (user_list_rd_lock(p_user_list_pool->semid) < 0)
1367 {
1368 log_error("user_list_rd_lock() error\n");
1369 return -2;
1370 }
1371
1372 left = 0;
1373 right = p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].user_count - 1;
1374
1375 while (left < right)
1376 {
1377 mid = (left + right) / 2;
1378 if (start_uid < p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].index_uid[mid].uid)
1379 {
1380 right = mid - 1;
1381 }
1382 else if (start_uid > p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].index_uid[mid].uid)
1383 {
1384 left = mid + 1;
1385 }
1386 else // if (start_uid == p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].index_uid[mid].uid)
1387 {
1388 left = mid;
1389 break;
1390 }
1391 }
1392
1393 for (i = 0; i < *p_user_cnt && left + i < p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].user_count; i++)
1394 {
1395 p_uid_list[i] = p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].index_uid[left + i].uid;
1396 }
1397 *p_user_cnt = i;
1398
1399 // release lock of user list
1400 if (user_list_rd_unlock(p_user_list_pool->semid) < 0)
1401 {
1402 log_error("user_list_rd_unlock() error\n");
1403 ret = -1;
1404 }
1405
1406 return ret;
1407 }
1408
1409 int user_stat_update(void)
1410 {
1411 return user_stat_map_update(&(p_user_list_pool->user_stat_map));
1412 }
1413
1414 int user_article_cnt_inc(int32_t uid, int n)
1415 {
1416 return user_stat_article_cnt_inc(&(p_user_list_pool->user_stat_map), uid, n);
1417 }
1418
1419 int get_user_article_cnt(int32_t uid)
1420 {
1421 const USER_STAT *p_stat;
1422 int ret;
1423
1424 ret = user_stat_get(&(p_user_list_pool->user_stat_map), uid, &p_stat);
1425 if (ret < 0)
1426 {
1427 log_error("user_stat_get(uid=%d) error: %d\n", uid, ret);
1428 return -1;
1429 }
1430 else if (ret == 0) // user not found
1431 {
1432 return -1;
1433 }
1434
1435 return p_stat->article_count;
1436 }

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