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

Annotation of /lbbs/src/user_list.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.2 - (hide 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 sysadm 1.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 sysadm 1.2 #include <sys/param.h>
28 sysadm 1.1 #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 sysadm 1.2 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 sysadm 1.1 }
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 sysadm 1.2
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