/[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.2 - (show annotations)
Tue Oct 21 11:11:28 2025 UTC (4 months, 3 weeks ago) by sysadm
Branch: MAIN
Changes since 1.1: +59 -0 lines
Content type: text/x-csrc
Add user_list_display

1 /***************************************************************************
2 user_list.c - description
3 -------------------
4 Copyright : (C) 2004-2025 by Leaflet
5 Email : leaflet@leafok.com
6 ***************************************************************************/
7
8 /***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 3 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16
17 #include "common.h"
18 #include "database.h"
19 #include "log.h"
20 #include "user_list.h"
21 #include <errno.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <time.h>
25 #include <sys/ipc.h>
26 #include <sys/mman.h>
27 #include <sys/param.h>
28 #include <sys/sem.h>
29 #include <sys/shm.h>
30
31 #ifdef _SEM_SEMUN_UNDEFINED
32 union semun
33 {
34 int val; /* Value for SETVAL */
35 struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
36 unsigned short *array; /* Array for GETALL, SETALL */
37 struct seminfo *__buf; /* Buffer for IPC_INFO
38 (Linux-specific) */
39 };
40 #endif // #ifdef _SEM_SEMUN_UNDEFINED
41
42 #define USER_LIST_TRY_LOCK_WAIT_TIME 1 // second
43 #define USER_LIST_TRY_LOCK_TIMES 10
44
45 struct user_list_pool_t
46 {
47 int shmid;
48 int semid;
49 USER_LIST user_list[2];
50 USER_LIST *p_current;
51 USER_LIST *p_new;
52 };
53 typedef struct user_list_pool_t USER_LIST_POOL;
54
55 static USER_LIST_POOL *p_user_list_pool = NULL;
56
57 int user_list_load(MYSQL *db, USER_LIST *p_list)
58 {
59 MYSQL_RES *rs = NULL;
60 MYSQL_ROW row;
61 char sql[SQL_BUFFER_LEN];
62 int ret = 0;
63 int i;
64
65 if (db == NULL || p_list == NULL)
66 {
67 log_error("NULL pointer error\n");
68 return -1;
69 }
70
71 snprintf(sql, sizeof(sql),
72 "SELECT user_list.UID AS UID, username, nickname, gender, gender_pub, life, exp, "
73 "UNIX_TIMESTAMP(signup_dt), UNIX_TIMESTAMP(last_login_dt), UNIX_TIMESTAMP(birthday) "
74 "FROM user_list INNER JOIN user_pubinfo ON user_list.UID = user_pubinfo.UID "
75 "INNER JOIN user_reginfo ON user_list.UID = user_reginfo.UID "
76 "WHERE enable ORDER BY UID");
77
78 if (mysql_query(db, sql) != 0)
79 {
80 log_error("Query user info error: %s\n", mysql_error(db));
81 ret = -1;
82 goto cleanup;
83 }
84
85 if ((rs = mysql_use_result(db)) == NULL)
86 {
87 log_error("Get user info data failed\n");
88 ret = -1;
89 goto cleanup;
90 }
91
92 i = 0;
93 while ((row = mysql_fetch_row(rs)))
94 {
95 p_list->users[i].uid = atoi(row[0]);
96 strncpy(p_list->users[i].username, row[1], sizeof(p_list->users[i].username) - 1);
97 p_list->users[i].username[sizeof(p_list->users[i].username) - 1] = '\0';
98 strncpy(p_list->users[i].nickname, row[2], sizeof(p_list->users[i].nickname) - 1);
99 p_list->users[i].nickname[sizeof(p_list->users[i].nickname) - 1] = '\0';
100 p_list->users[i].gender = row[3][0];
101 p_list->users[i].gender_pub = (int8_t)(row[4] == NULL ? 0 : atoi(row[4]));
102 p_list->users[i].life = (row[5] == NULL ? 0 : atoi(row[5]));
103 p_list->users[i].exp = (row[6] == NULL ? 0 : atoi(row[6]));
104 p_list->users[i].signup_dt = (row[7] == NULL ? 0 : atol(row[7]));
105 p_list->users[i].last_login_dt = (row[8] == NULL ? 0 : atol(row[8]));
106 p_list->users[i].birthday = (row[9] == NULL ? 0 : atol(row[9]));
107
108 i++;
109 if (i >= BBS_max_user_count)
110 {
111 log_error("Too many users, exceed limit %d\n", BBS_max_user_count);
112 break;
113 }
114 }
115 mysql_free_result(rs);
116 rs = NULL;
117
118 p_list->user_count = i;
119
120 #ifdef _DEBUG
121 log_error("Loaded %d users\n", p_list->user_count);
122 #endif
123
124 cleanup:
125 mysql_free_result(rs);
126
127 return ret;
128 }
129
130 int user_list_pool_init(void)
131 {
132 int shmid;
133 int semid;
134 int proj_id;
135 key_t key;
136 size_t size;
137 void *p_shm;
138 union semun arg;
139 int i;
140
141 if (p_user_list_pool != NULL)
142 {
143 log_error("p_user_list_pool already initialized\n");
144 return -1;
145 }
146
147 // Allocate shared memory
148 proj_id = (int)(time(NULL) % getpid());
149 key = ftok(VAR_USER_LIST_SHM, proj_id);
150 if (key == -1)
151 {
152 log_error("ftok(%s %d) error (%d)\n", VAR_USER_LIST_SHM, proj_id, errno);
153 return -2;
154 }
155
156 size = sizeof(USER_LIST_POOL);
157 shmid = shmget(key, size, IPC_CREAT | IPC_EXCL | 0600);
158 if (shmid == -1)
159 {
160 log_error("shmget(size = %d) error (%d)\n", size, errno);
161 return -3;
162 }
163 p_shm = shmat(shmid, NULL, 0);
164 if (p_shm == (void *)-1)
165 {
166 log_error("shmat(shmid=%d) error (%d)\n", shmid, errno);
167 return -3;
168 }
169
170 p_user_list_pool = p_shm;
171 p_user_list_pool->shmid = shmid;
172
173 // Allocate semaphore as user list pool lock
174 size = 2; // r_sem and w_sem
175 semid = semget(key, (int)size, IPC_CREAT | IPC_EXCL | 0600);
176 if (semid == -1)
177 {
178 log_error("semget(user_list_pool_sem, size = %d) error (%d)\n", size, errno);
179 return -3;
180 }
181
182 // Initialize sem value to 0
183 arg.val = 0;
184 for (i = 0; i < size; i++)
185 {
186 if (semctl(semid, i, SETVAL, arg) == -1)
187 {
188 log_error("semctl(user_list_pool_sem, SETVAL) error (%d)\n", errno);
189 return -3;
190 }
191 }
192
193 p_user_list_pool->semid = semid;
194
195 // Set user counts to 0
196 p_user_list_pool->user_list[0].user_count = 0;
197 p_user_list_pool->user_list[1].user_count = 0;
198
199 p_user_list_pool->p_current = &(p_user_list_pool->user_list[0]);
200 p_user_list_pool->p_new = &(p_user_list_pool->user_list[1]);
201
202 return 0;
203 }
204
205 void user_list_pool_cleanup(void)
206 {
207 int shmid;
208
209 if (p_user_list_pool == NULL)
210 {
211 return;
212 }
213
214 shmid = p_user_list_pool->shmid;
215
216 if (semctl(p_user_list_pool->semid, 0, IPC_RMID) == -1)
217 {
218 log_error("semctl(semid = %d, IPC_RMID) error (%d)\n", p_user_list_pool->semid, errno);
219 }
220
221 if (shmdt(p_user_list_pool) == -1)
222 {
223 log_error("shmdt(shmid = %d) error (%d)\n", shmid, errno);
224 }
225
226 if (shmctl(shmid, IPC_RMID, NULL) == -1)
227 {
228 log_error("shmctl(shmid = %d, IPC_RMID) error (%d)\n", shmid, errno);
229 }
230
231 p_user_list_pool = NULL;
232 }
233
234 int set_user_list_pool_shm_readonly(void)
235 {
236 int shmid;
237 void *p_shm;
238
239 if (p_user_list_pool == NULL)
240 {
241 log_error("p_user_list_pool not initialized\n");
242 return -1;
243 }
244
245 shmid = p_user_list_pool->shmid;
246
247 // Remap shared memory in read-only mode
248 p_shm = shmat(shmid, p_user_list_pool, SHM_RDONLY | SHM_REMAP);
249 if (p_shm == (void *)-1)
250 {
251 log_error("shmat(user_list_pool shmid = %d) error (%d)\n", shmid, errno);
252 return -3;
253 }
254
255 p_user_list_pool = p_shm;
256
257 return 0;
258 }
259
260 int detach_user_list_pool_shm(void)
261 {
262 if (p_user_list_pool != NULL && shmdt(p_user_list_pool) == -1)
263 {
264 log_error("shmdt(user_list_pool) error (%d)\n", errno);
265 return -1;
266 }
267
268 p_user_list_pool = NULL;
269
270 return 0;
271 }
272
273 int user_list_pool_reload(void)
274 {
275 MYSQL *db = NULL;
276 USER_LIST *p_tmp;
277 int ret = 0;
278
279 if (p_user_list_pool == NULL)
280 {
281 log_error("p_user_list_pool not initialized\n");
282 return -1;
283 }
284
285 db = db_open();
286 if (db == NULL)
287 {
288 log_error("db_open() error: %s\n", mysql_error(db));
289 return -1;
290 }
291
292 if (user_list_rw_lock() < 0)
293 {
294 log_error("user_list_rw_lock() error\n");
295 return -2;
296 }
297
298 if (user_list_load(db, p_user_list_pool->p_new) < 0)
299 {
300 log_error("user_list_rw_lock() error\n");
301 ret = -3;
302 goto cleanup;
303 }
304
305 // Swap p_current and p_new
306 p_tmp = p_user_list_pool->p_current;
307 p_user_list_pool->p_current = p_user_list_pool->p_new;
308 p_user_list_pool->p_new = p_tmp;
309
310 cleanup:
311 if (user_list_rw_unlock() < 0)
312 {
313 log_error("user_list_rw_unlock() error\n");
314 ret = -2;
315 }
316
317 mysql_close(db);
318
319 return ret;
320 }
321
322 int user_list_try_rd_lock(int wait_sec)
323 {
324 struct sembuf sops[2];
325 struct timespec timeout;
326 int ret;
327
328 sops[0].sem_num = 1; // w_sem
329 sops[0].sem_op = 0; // wait until unlocked
330 sops[0].sem_flg = 0;
331
332 sops[1].sem_num = 0; // r_sem
333 sops[1].sem_op = 1; // lock
334 sops[1].sem_flg = SEM_UNDO; // undo on terminate
335
336 timeout.tv_sec = wait_sec;
337 timeout.tv_nsec = 0;
338
339 ret = semtimedop(p_user_list_pool->semid, sops, 2, &timeout);
340 if (ret == -1 && errno != EAGAIN && errno != EINTR)
341 {
342 log_error("semtimedop(lock read) error %d\n", errno);
343 }
344
345 return ret;
346 }
347
348 int user_list_try_rw_lock(int wait_sec)
349 {
350 struct sembuf sops[3];
351 struct timespec timeout;
352 int ret;
353
354 sops[0].sem_num = 1; // w_sem
355 sops[0].sem_op = 0; // wait until unlocked
356 sops[0].sem_flg = 0;
357
358 sops[1].sem_num = 1; // w_sem
359 sops[1].sem_op = 1; // lock
360 sops[1].sem_flg = SEM_UNDO; // undo on terminate
361
362 sops[2].sem_num = 0; // r_sem
363 sops[2].sem_op = 0; // wait until unlocked
364 sops[2].sem_flg = 0;
365
366 timeout.tv_sec = wait_sec;
367 timeout.tv_nsec = 0;
368
369 ret = semtimedop(p_user_list_pool->semid, sops, 3, &timeout);
370 if (ret == -1 && errno != EAGAIN && errno != EINTR)
371 {
372 log_error("semtimedop(lock write) error %d\n", errno);
373 }
374
375 return ret;
376 }
377
378 int user_list_rd_unlock(void)
379 {
380 struct sembuf sops[2];
381 int ret;
382
383 sops[0].sem_num = 0; // r_sem
384 sops[0].sem_op = -1; // unlock
385 sops[0].sem_flg = IPC_NOWAIT | SEM_UNDO; // no wait
386
387 ret = semop(p_user_list_pool->semid, sops, 1);
388 if (ret == -1 && errno != EAGAIN && errno != EINTR)
389 {
390 log_error("semop(unlock read) error %d\n", errno);
391 }
392
393 return ret;
394 }
395
396 int user_list_rw_unlock(void)
397 {
398 struct sembuf sops[1];
399 int ret;
400
401 sops[0].sem_num = 1; // w_sem
402 sops[0].sem_op = -1; // unlock
403 sops[0].sem_flg = IPC_NOWAIT | SEM_UNDO; // no wait
404
405 ret = semop(p_user_list_pool->semid, sops, 1);
406 if (ret == -1 && errno != EAGAIN && errno != EINTR)
407 {
408 log_error("semop(unlock write) error %d\n", errno);
409 }
410
411 return ret;
412 }
413
414 int user_list_rd_lock(void)
415 {
416 int timer = 0;
417 int ret = -1;
418
419 while (!SYS_server_exit)
420 {
421 ret = user_list_try_rd_lock(USER_LIST_TRY_LOCK_WAIT_TIME);
422 if (ret == 0) // success
423 {
424 break;
425 }
426 else if (errno == EAGAIN || errno == EINTR) // retry
427 {
428 timer++;
429 if (timer % USER_LIST_TRY_LOCK_TIMES == 0)
430 {
431 log_error("user_list_try_rd_lock() tried %d times\n", timer);
432 }
433 }
434 else // failed
435 {
436 log_error("user_list_try_rd_lock() failed\n");
437 break;
438 }
439 }
440
441 return ret;
442 }
443
444 int user_list_rw_lock(void)
445 {
446 int timer = 0;
447 int ret = -1;
448
449 while (!SYS_server_exit)
450 {
451 ret = user_list_try_rw_lock(USER_LIST_TRY_LOCK_WAIT_TIME);
452 if (ret == 0) // success
453 {
454 break;
455 }
456 else if (errno == EAGAIN || errno == EINTR) // retry
457 {
458 timer++;
459 if (timer % USER_LIST_TRY_LOCK_TIMES == 0)
460 {
461 log_error("user_list_try_rw_lock() tried %d times\n", timer);
462 }
463 }
464 else // failed
465 {
466 log_error("user_list_try_rw_lock() failed\n");
467 break;
468 }
469 }
470
471 return ret;
472 }
473
474 int query_user_list(int page_id, USER_INFO *p_users, int *p_user_count, int *p_page_count)
475 {
476 int ret = 0;
477
478 if (p_users == NULL || p_user_count == NULL || p_page_count == NULL)
479 {
480 log_error("NULL pointer error\n");
481 return -1;
482 }
483
484 // acquire lock of user list
485 if (user_list_rd_lock() < 0)
486 {
487 log_error("user_list_rd_lock() error\n");
488 return -2;
489 }
490
491 if (p_user_list_pool->p_current->user_count == 0)
492 {
493 // empty list
494 ret = 0;
495 goto cleanup;
496 }
497
498 *p_page_count = p_user_list_pool->p_current->user_count / BBS_user_limit_per_page +
499 (p_user_list_pool->p_current->user_count % BBS_user_limit_per_page == 0 ? 0 : 1);
500
501 if (page_id < 0 || page_id >= *p_page_count)
502 {
503 log_error("Invalid page_id = %d, not in range [0, %d)\n", page_id, *p_page_count);
504 ret = -3;
505 goto cleanup;
506 }
507
508 *p_user_count = MIN(BBS_user_limit_per_page,
509 p_user_list_pool->p_current->user_count -
510 page_id * BBS_user_limit_per_page);
511
512 memcpy(p_users,
513 p_user_list_pool->p_current->users + page_id * BBS_user_limit_per_page,
514 sizeof(USER_INFO) * (size_t)(*p_user_count));
515
516 cleanup:
517 // release lock of user list
518 if (user_list_rd_unlock() < 0)
519 {
520 log_error("user_list_rd_unlock() error\n");
521 ret = -1;
522 }
523
524 return ret;
525 }

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