/[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.38 - (show annotations)
Tue Nov 18 15:15:18 2025 UTC (3 months, 4 weeks ago) by sysadm
Branch: MAIN
Changes since 1.37: +6 -13 lines
Content type: text/x-csrc
Skip remap shared memory in read only mode under Cygwin
shmat() again after shmdt() without SHM_REMAP would change the address

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 #ifndef __CYGWIN__
523 int shmid;
524 void *p_shm;
525
526 if (p_user_list_pool == NULL)
527 {
528 log_error("p_user_list_pool not initialized\n");
529 return -1;
530 }
531
532 shmid = p_user_list_pool->shmid;
533
534 // Remap shared memory in read-only mode
535 p_shm = shmat(shmid, p_user_list_pool, SHM_RDONLY | SHM_REMAP);
536 if (p_shm == (void *)-1)
537 {
538 log_error("shmat(user_list_pool shmid = %d) error (%d)\n", shmid, errno);
539 return -3;
540 }
541
542 p_user_list_pool = p_shm;
543 #endif
544
545 return 0;
546 }
547
548 int detach_user_list_pool_shm(void)
549 {
550 if (p_user_list_pool != NULL && shmdt(p_user_list_pool) == -1)
551 {
552 log_error("shmdt(user_list_pool) error (%d)\n", errno);
553 return -1;
554 }
555
556 p_user_list_pool = NULL;
557
558 return 0;
559 }
560
561 int user_list_pool_reload(int online_user)
562 {
563 MYSQL *db = NULL;
564 int tmp;
565 int ret = 0;
566
567 if (p_user_list_pool == NULL)
568 {
569 log_error("p_user_list_pool not initialized\n");
570 return -1;
571 }
572
573 db = db_open();
574 if (db == NULL)
575 {
576 log_error("db_open() error: %s\n", mysql_error(db));
577 return -1;
578 }
579
580 if (online_user)
581 {
582 if (user_online_list_load(db, &(p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_new])) < 0)
583 {
584 log_error("user_online_list_load() error\n");
585 ret = -2;
586 goto cleanup;
587 }
588
589 if (user_login_count_load(db) < 0)
590 {
591 log_error("user_login_count_load() error\n");
592 ret = -2;
593 goto cleanup;
594 }
595 }
596 else
597 {
598 if (user_list_load(db, &(p_user_list_pool->user_list[p_user_list_pool->user_list_index_new])) < 0)
599 {
600 log_error("user_list_load() error\n");
601 ret = -2;
602 goto cleanup;
603 }
604 }
605
606 mysql_close(db);
607 db = NULL;
608
609 if (user_list_rw_lock(p_user_list_pool->semid) < 0)
610 {
611 log_error("user_list_rw_lock() error\n");
612 ret = -3;
613 goto cleanup;
614 }
615
616 if (online_user)
617 {
618 // Swap p_online_current and p_online_new
619 tmp = p_user_list_pool->user_online_list_index_current;
620 p_user_list_pool->user_online_list_index_current = p_user_list_pool->user_online_list_index_new;
621 p_user_list_pool->user_online_list_index_new = tmp;
622 }
623 else
624 {
625 // Swap index_current and index_new
626 tmp = p_user_list_pool->user_list_index_current;
627 p_user_list_pool->user_list_index_current = p_user_list_pool->user_list_index_new;
628 p_user_list_pool->user_list_index_new = tmp;
629 }
630
631 if (user_list_rw_unlock(p_user_list_pool->semid) < 0)
632 {
633 log_error("user_list_rw_unlock() error\n");
634 ret = -3;
635 goto cleanup;
636 }
637
638 cleanup:
639 mysql_close(db);
640
641 return ret;
642 }
643
644 int user_list_try_rd_lock(int semid, int wait_sec)
645 {
646 struct sembuf sops[2];
647 #ifndef __CYGWIN__
648 struct timespec timeout;
649 #endif
650 int ret;
651
652 sops[0].sem_num = 1; // w_sem
653 sops[0].sem_op = 0; // wait until unlocked
654 sops[0].sem_flg = 0;
655
656 sops[1].sem_num = 0; // r_sem
657 sops[1].sem_op = 1; // lock
658 sops[1].sem_flg = SEM_UNDO; // undo on terminate
659
660 #ifdef __CYGWIN__
661 ret = semop(semid, sops, 2);
662 #else
663 timeout.tv_sec = wait_sec;
664 timeout.tv_nsec = 0;
665
666 ret = semtimedop(semid, sops, 2, &timeout);
667 #endif
668 if (ret == -1 && errno != EAGAIN && errno != EINTR)
669 {
670 log_error("semop(lock read) error %d\n", errno);
671 }
672
673 return ret;
674 }
675
676 int user_list_try_rw_lock(int semid, int wait_sec)
677 {
678 struct sembuf sops[3];
679 #ifndef __CYGWIN__
680 struct timespec timeout;
681 #endif
682 int ret;
683
684 sops[0].sem_num = 1; // w_sem
685 sops[0].sem_op = 0; // wait until unlocked
686 sops[0].sem_flg = 0;
687
688 sops[1].sem_num = 1; // w_sem
689 sops[1].sem_op = 1; // lock
690 sops[1].sem_flg = SEM_UNDO; // undo on terminate
691
692 sops[2].sem_num = 0; // r_sem
693 sops[2].sem_op = 0; // wait until unlocked
694 sops[2].sem_flg = 0;
695
696 #ifdef __CYGWIN__
697 ret = semop(semid, sops, 3);
698 #else
699 timeout.tv_sec = wait_sec;
700 timeout.tv_nsec = 0;
701
702 ret = semtimedop(semid, sops, 3, &timeout);
703 #endif
704 if (ret == -1 && errno != EAGAIN && errno != EINTR)
705 {
706 log_error("semop(lock write) error %d\n", errno);
707 }
708
709 return ret;
710 }
711
712 int user_list_rd_unlock(int semid)
713 {
714 struct sembuf sops[2];
715 int ret;
716
717 sops[0].sem_num = 0; // r_sem
718 sops[0].sem_op = -1; // unlock
719 sops[0].sem_flg = IPC_NOWAIT | SEM_UNDO; // no wait
720
721 ret = semop(semid, sops, 1);
722 if (ret == -1 && errno != EAGAIN && errno != EINTR)
723 {
724 log_error("semop(unlock read) error %d\n", errno);
725 }
726
727 return ret;
728 }
729
730 int user_list_rw_unlock(int semid)
731 {
732 struct sembuf sops[1];
733 int ret;
734
735 sops[0].sem_num = 1; // w_sem
736 sops[0].sem_op = -1; // unlock
737 sops[0].sem_flg = IPC_NOWAIT | SEM_UNDO; // no wait
738
739 ret = semop(semid, sops, 1);
740 if (ret == -1 && errno != EAGAIN && errno != EINTR)
741 {
742 log_error("semop(unlock write) error %d\n", errno);
743 }
744
745 return ret;
746 }
747
748 int user_list_rd_lock(int semid)
749 {
750 int timer = 0;
751 int ret = -1;
752
753 while (!SYS_server_exit)
754 {
755 ret = user_list_try_rd_lock(semid, USER_LIST_TRY_LOCK_WAIT_TIME);
756 if (ret == 0) // success
757 {
758 break;
759 }
760 else if (errno == EAGAIN || errno == EINTR) // retry
761 {
762 timer++;
763 if (timer % USER_LIST_TRY_LOCK_TIMES == 0)
764 {
765 log_error("user_list_try_rd_lock() tried %d times\n", timer);
766 }
767 }
768 else // failed
769 {
770 log_error("user_list_try_rd_lock() failed\n");
771 break;
772 }
773 }
774
775 return ret;
776 }
777
778 int user_list_rw_lock(int semid)
779 {
780 int timer = 0;
781 int ret = -1;
782
783 while (!SYS_server_exit)
784 {
785 ret = user_list_try_rw_lock(semid, USER_LIST_TRY_LOCK_WAIT_TIME);
786 if (ret == 0) // success
787 {
788 break;
789 }
790 else if (errno == EAGAIN || errno == EINTR) // retry
791 {
792 timer++;
793 if (timer % USER_LIST_TRY_LOCK_TIMES == 0)
794 {
795 log_error("user_list_try_rw_lock() tried %d times\n", timer);
796 }
797 }
798 else // failed
799 {
800 log_error("user_list_try_rw_lock() failed\n");
801 break;
802 }
803 }
804
805 return ret;
806 }
807
808 int query_user_list(int page_id, USER_INFO *p_users, int *p_user_count, int *p_page_count)
809 {
810 int ret = 0;
811
812 if (p_users == NULL || p_user_count == NULL || p_page_count == NULL)
813 {
814 log_error("NULL pointer error\n");
815 return -1;
816 }
817
818 *p_user_count = 0;
819 *p_page_count = 0;
820
821 // acquire lock of user list
822 if (user_list_rd_lock(p_user_list_pool->semid) < 0)
823 {
824 log_error("user_list_rd_lock() error\n");
825 return -2;
826 }
827
828 if (p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].user_count == 0)
829 {
830 // empty list
831 ret = 0;
832 goto cleanup;
833 }
834
835 *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) /
836 BBS_user_limit_per_page;
837
838 if (page_id < 0 || page_id >= *p_page_count)
839 {
840 log_error("Invalid page_id = %d, not in range [0, %d)\n", page_id, *p_page_count);
841 ret = -3;
842 goto cleanup;
843 }
844
845 *p_user_count = MIN(BBS_user_limit_per_page,
846 p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].user_count -
847 page_id * BBS_user_limit_per_page);
848
849 memcpy(p_users,
850 p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].users + page_id * BBS_user_limit_per_page,
851 sizeof(USER_INFO) * (size_t)(*p_user_count));
852
853 cleanup:
854 // release lock of user list
855 if (user_list_rd_unlock(p_user_list_pool->semid) < 0)
856 {
857 log_error("user_list_rd_unlock() error\n");
858 ret = -1;
859 }
860
861 return ret;
862 }
863
864 int query_user_online_list(int page_id, USER_ONLINE_INFO *p_online_users, int *p_user_count, int *p_page_count)
865 {
866 int ret = 0;
867
868 if (p_online_users == NULL || p_user_count == NULL || p_page_count == NULL)
869 {
870 log_error("NULL pointer error\n");
871 return -1;
872 }
873
874 *p_user_count = 0;
875 *p_page_count = 0;
876
877 // acquire lock of user list
878 if (user_list_rd_lock(p_user_list_pool->semid) < 0)
879 {
880 log_error("user_list_rd_lock() error\n");
881 return -2;
882 }
883
884 if (p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].user_count == 0)
885 {
886 // empty list
887 ret = 0;
888 goto cleanup;
889 }
890
891 *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;
892
893 if (page_id < 0 || page_id >= *p_page_count)
894 {
895 log_error("Invalid page_id = %d, not in range [0, %d)\n", page_id, *p_page_count);
896 ret = -3;
897 goto cleanup;
898 }
899
900 *p_user_count = MIN(BBS_user_limit_per_page,
901 p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].user_count -
902 page_id * BBS_user_limit_per_page);
903
904 memcpy(p_online_users,
905 p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].users + page_id * BBS_user_limit_per_page,
906 sizeof(USER_ONLINE_INFO) * (size_t)(*p_user_count));
907
908 cleanup:
909 // release lock of user list
910 if (user_list_rd_unlock(p_user_list_pool->semid) < 0)
911 {
912 log_error("user_list_rd_unlock() error\n");
913 ret = -1;
914 }
915
916 return ret;
917 }
918
919 int get_user_list_count(int *p_user_cnt)
920 {
921 if (p_user_cnt == NULL)
922 {
923 log_error("NULL pointer error\n");
924 return -1;
925 }
926
927 // acquire lock of user list
928 if (user_list_rd_lock(p_user_list_pool->semid) < 0)
929 {
930 log_error("user_list_rd_lock() error\n");
931 return -2;
932 }
933
934 *p_user_cnt = p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].user_count;
935
936 // release lock of user list
937 if (user_list_rd_unlock(p_user_list_pool->semid) < 0)
938 {
939 log_error("user_list_rd_unlock() error\n");
940 return -2;
941 }
942
943 return 0;
944 }
945
946 int get_user_online_list_count(int *p_user_cnt, int *p_guest_cnt)
947 {
948 if (p_user_cnt == NULL || p_guest_cnt == NULL)
949 {
950 log_error("NULL pointer error\n");
951 return -1;
952 }
953
954 // acquire lock of user list
955 if (user_list_rd_lock(p_user_list_pool->semid) < 0)
956 {
957 log_error("user_list_rd_lock() error\n");
958 return -2;
959 }
960
961 *p_user_cnt = p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].user_count;
962 *p_guest_cnt = p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].guest_count;
963
964 // release lock of user list
965 if (user_list_rd_unlock(p_user_list_pool->semid) < 0)
966 {
967 log_error("user_list_rd_unlock() error\n");
968 return -2;
969 }
970
971 return 0;
972 }
973
974 int get_user_login_count(int *p_login_cnt)
975 {
976 if (p_login_cnt == NULL)
977 {
978 log_error("NULL pointer error\n");
979 return -1;
980 }
981
982 *p_login_cnt = p_user_list_pool->user_login_count;
983
984 return 0;
985 }
986
987 int query_user_info(int32_t id, USER_INFO *p_user)
988 {
989 int ret = 0;
990
991 if (p_user == NULL)
992 {
993 log_error("NULL pointer error\n");
994 return -1;
995 }
996
997 // acquire lock of user list
998 if (user_list_rd_lock(p_user_list_pool->semid) < 0)
999 {
1000 log_error("user_list_rd_lock() error\n");
1001 return -2;
1002 }
1003
1004 if (id >= 0 && id < p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].user_count) // Found
1005 {
1006 *p_user = p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].users[id];
1007 ret = 1;
1008 }
1009
1010 // release lock of user list
1011 if (user_list_rd_unlock(p_user_list_pool->semid) < 0)
1012 {
1013 log_error("user_list_rd_unlock() error\n");
1014 ret = -1;
1015 }
1016
1017 return ret;
1018 }
1019
1020 int query_user_info_by_uid(int32_t uid, USER_INFO *p_user, char *p_intro_buf, size_t intro_buf_len)
1021 {
1022 int left;
1023 int right;
1024 int mid;
1025 int32_t id;
1026 int ret = 0;
1027
1028 if (p_user == NULL)
1029 {
1030 log_error("NULL pointer error\n");
1031 return -1;
1032 }
1033
1034 // acquire lock of user list
1035 if (user_list_rd_lock(p_user_list_pool->semid) < 0)
1036 {
1037 log_error("user_list_rd_lock() error\n");
1038 return -2;
1039 }
1040
1041 left = 0;
1042 right = p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].user_count - 1;
1043
1044 while (left < right)
1045 {
1046 mid = (left + right) / 2;
1047 if (uid < p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].index_uid[mid].uid)
1048 {
1049 right = mid - 1;
1050 }
1051 else if (uid > p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].index_uid[mid].uid)
1052 {
1053 left = mid + 1;
1054 }
1055 else // if (uid == p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].index_uid[mid].uid)
1056 {
1057 left = mid;
1058 break;
1059 }
1060 }
1061
1062 if (uid == p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].index_uid[left].uid) // Found
1063 {
1064 id = p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].index_uid[left].id;
1065 *p_user = p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].users[id];
1066 ret = 1;
1067
1068 if (p_intro_buf != NULL)
1069 {
1070 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);
1071 p_intro_buf[intro_buf_len - 1] = '\0';
1072 p_user->intro = p_intro_buf;
1073 }
1074 }
1075
1076 // release lock of user list
1077 if (user_list_rd_unlock(p_user_list_pool->semid) < 0)
1078 {
1079 log_error("user_list_rd_unlock() error\n");
1080 ret = -1;
1081 }
1082
1083 return ret;
1084 }
1085
1086 int query_user_info_by_username(const char *username_prefix, int max_user_cnt,
1087 int32_t uid_list[], char username_list[][BBS_username_max_len + 1])
1088 {
1089 int left;
1090 int right;
1091 int mid;
1092 int left_save;
1093 int ret = 0;
1094 size_t prefix_len;
1095 int comp;
1096 int i;
1097
1098 if (username_prefix == NULL || uid_list == NULL || username_list == NULL)
1099 {
1100 log_error("NULL pointer error\n");
1101 return -1;
1102 }
1103
1104 prefix_len = strlen(username_prefix);
1105
1106 // acquire lock of user list
1107 if (user_list_rd_lock(p_user_list_pool->semid) < 0)
1108 {
1109 log_error("user_list_rd_lock() error\n");
1110 return -2;
1111 }
1112
1113 left = 0;
1114 right = p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].user_count - 1;
1115
1116 while (left < right)
1117 {
1118 mid = (left + right) / 2;
1119 comp = strncasecmp(username_prefix, p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].users[mid].username, prefix_len);
1120 if (comp < 0)
1121 {
1122 right = mid - 1;
1123 }
1124 else if (comp > 0)
1125 {
1126 left = mid + 1;
1127 }
1128 else // if (comp == 0)
1129 {
1130 left = mid;
1131 break;
1132 }
1133 }
1134
1135 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
1136 {
1137 #ifdef _DEBUG
1138 log_error("Debug: match found, pos=%d\n", left);
1139 #endif
1140
1141 left_save = left;
1142 right = left;
1143 left = 0;
1144
1145 while (left < right)
1146 {
1147 mid = (left + right) / 2;
1148 comp = strncasecmp(username_prefix, p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].users[mid].username, prefix_len);
1149 if (comp > 0)
1150 {
1151 left = mid + 1;
1152 }
1153 else if (comp == 0)
1154 {
1155 right = mid;
1156 }
1157 else // if (comp < 0)
1158 {
1159 log_error("Bug: left=%d right=%d mid=%d");
1160 ret = -2;
1161 goto cleanup;
1162 }
1163 }
1164
1165 #ifdef _DEBUG
1166 log_error("Debug: first match found, pos=%d\n", right);
1167 #endif
1168
1169 left = left_save;
1170 left_save = right;
1171 right = p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].user_count - 1;
1172
1173 while (left < right)
1174 {
1175 mid = (left + right) / 2 + (left + right) % 2;
1176 comp = strncasecmp(username_prefix, p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].users[mid].username, prefix_len);
1177 if (comp < 0)
1178 {
1179 right = mid - 1;
1180 }
1181 else if (comp == 0)
1182 {
1183 left = mid;
1184 }
1185 else // if (comp > 0)
1186 {
1187 log_error("Bug: left=%d right=%d mid=%d");
1188 ret = -2;
1189 goto cleanup;
1190 }
1191 }
1192
1193 #ifdef _DEBUG
1194 log_error("Debug: last match found, pos=%d\n", left);
1195 #endif
1196
1197 right = left;
1198 left = left_save;
1199
1200 for (i = 0; i < max_user_cnt && left + i <= right; i++)
1201 {
1202 uid_list[i] = p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].users[left + i].uid;
1203 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);
1204 username_list[i][sizeof(username_list[i]) - 1] = '\0';
1205 }
1206 ret = i;
1207 }
1208
1209 cleanup:
1210 // release lock of user list
1211 if (user_list_rd_unlock(p_user_list_pool->semid) < 0)
1212 {
1213 log_error("user_list_rd_unlock() error\n");
1214 ret = -1;
1215 }
1216
1217 return ret;
1218 }
1219
1220 int query_user_online_info(int32_t id, USER_ONLINE_INFO *p_user)
1221 {
1222 int ret = 0;
1223
1224 if (p_user == NULL)
1225 {
1226 log_error("NULL pointer error\n");
1227 return -1;
1228 }
1229
1230 // acquire lock of user list
1231 if (user_list_rd_lock(p_user_list_pool->semid) < 0)
1232 {
1233 log_error("user_list_rd_lock() error\n");
1234 return -2;
1235 }
1236
1237 if (id >= 0 && id < p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].user_count) // Found
1238 {
1239 *p_user = p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].users[id];
1240 ret = 1;
1241 }
1242
1243 // release lock of user list
1244 if (user_list_rd_unlock(p_user_list_pool->semid) < 0)
1245 {
1246 log_error("user_list_rd_unlock() error\n");
1247 ret = -1;
1248 }
1249
1250 return ret;
1251 }
1252
1253 int query_user_online_info_by_uid(int32_t uid, USER_ONLINE_INFO *p_users, int *p_user_cnt, int start_id)
1254 {
1255 int left;
1256 int right;
1257 int mid;
1258 int32_t id;
1259 int ret = 0;
1260 int i;
1261 int user_cnt;
1262
1263 if (p_users == NULL || p_user_cnt == NULL)
1264 {
1265 log_error("NULL pointer error\n");
1266 return -1;
1267 }
1268
1269 user_cnt = *p_user_cnt;
1270 *p_user_cnt = 0;
1271
1272 // acquire lock of user list
1273 if (user_list_rd_lock(p_user_list_pool->semid) < 0)
1274 {
1275 log_error("user_list_rd_lock() error\n");
1276 return -2;
1277 }
1278
1279 left = start_id;
1280 right = p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].user_count - 1;
1281
1282 while (left < right)
1283 {
1284 mid = (left + right) / 2;
1285 if (uid < p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].index_uid[mid].uid)
1286 {
1287 right = mid - 1;
1288 }
1289 else if (uid > p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].index_uid[mid].uid)
1290 {
1291 left = mid + 1;
1292 }
1293 else // if (uid == p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].index_uid[mid].uid)
1294 {
1295 left = mid;
1296 break;
1297 }
1298 }
1299
1300 if (uid == p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].index_uid[left].uid)
1301 {
1302 right = left;
1303 left = start_id;
1304
1305 while (left < right)
1306 {
1307 mid = (left + right) / 2;
1308 if (uid - 1 < p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].index_uid[mid].uid)
1309 {
1310 right = mid;
1311 }
1312 else // if (uid - 1 >= p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].index_uid[mid].uid)
1313 {
1314 left = mid + 1;
1315 }
1316 }
1317
1318 for (i = 0;
1319 left < p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].user_count && i < user_cnt &&
1320 uid == p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].index_uid[left].uid;
1321 left++, i++)
1322 {
1323 id = p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].index_uid[left].id;
1324 p_users[i] = p_user_list_pool->user_online_list[p_user_list_pool->user_online_list_index_current].users[id];
1325 }
1326
1327 if (i > 0)
1328 {
1329 *p_user_cnt = i;
1330 ret = 1;
1331 }
1332 }
1333
1334 // release lock of user list
1335 if (user_list_rd_unlock(p_user_list_pool->semid) < 0)
1336 {
1337 log_error("user_list_rd_unlock() error\n");
1338 ret = -1;
1339 }
1340
1341 return ret;
1342 }
1343
1344 int get_user_id_list(int32_t *p_uid_list, int *p_user_cnt, int start_uid)
1345 {
1346 int left;
1347 int right;
1348 int mid;
1349 int ret = 0;
1350 int i;
1351
1352 if (p_uid_list == NULL || p_user_cnt == NULL)
1353 {
1354 log_error("NULL pointer error\n");
1355 return -1;
1356 }
1357
1358 // acquire lock of user list
1359 if (user_list_rd_lock(p_user_list_pool->semid) < 0)
1360 {
1361 log_error("user_list_rd_lock() error\n");
1362 return -2;
1363 }
1364
1365 left = 0;
1366 right = p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].user_count - 1;
1367
1368 while (left < right)
1369 {
1370 mid = (left + right) / 2;
1371 if (start_uid < p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].index_uid[mid].uid)
1372 {
1373 right = mid - 1;
1374 }
1375 else if (start_uid > p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].index_uid[mid].uid)
1376 {
1377 left = mid + 1;
1378 }
1379 else // if (start_uid == p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].index_uid[mid].uid)
1380 {
1381 left = mid;
1382 break;
1383 }
1384 }
1385
1386 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++)
1387 {
1388 p_uid_list[i] = p_user_list_pool->user_list[p_user_list_pool->user_list_index_current].index_uid[left + i].uid;
1389 }
1390 *p_user_cnt = i;
1391
1392 // release lock of user list
1393 if (user_list_rd_unlock(p_user_list_pool->semid) < 0)
1394 {
1395 log_error("user_list_rd_unlock() error\n");
1396 ret = -1;
1397 }
1398
1399 return ret;
1400 }
1401
1402 int user_stat_update(void)
1403 {
1404 return user_stat_map_update(&(p_user_list_pool->user_stat_map));
1405 }
1406
1407 int user_article_cnt_inc(int32_t uid, int n)
1408 {
1409 return user_stat_article_cnt_inc(&(p_user_list_pool->user_stat_map), uid, n);
1410 }
1411
1412 int get_user_article_cnt(int32_t uid)
1413 {
1414 const USER_STAT *p_stat;
1415 int ret;
1416
1417 ret = user_stat_get(&(p_user_list_pool->user_stat_map), uid, &p_stat);
1418 if (ret < 0)
1419 {
1420 log_error("user_stat_get(uid=%d) error: %d\n", uid, ret);
1421 return -1;
1422 }
1423 else if (ret == 0) // user not found
1424 {
1425 return -1;
1426 }
1427
1428 return p_stat->article_count;
1429 }

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