/[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.35 - (show annotations)
Mon Nov 17 12:16:48 2025 UTC (3 months, 4 weeks ago) by sysadm
Branch: MAIN
Changes since 1.34: +9 -0 lines
Content type: text/x-csrc
Add alternative implementation of shmat(..., SHM_REMAP) under MSYS2
Fix error reported by gcc under MSYS2

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

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