/[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.40 - (show annotations)
Thu Nov 20 02:56:46 2025 UTC (3 months, 3 weeks ago) by sysadm
Branch: MAIN
Changes since 1.39: +5 -1 lines
Content type: text/x-csrc
Update

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

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