/[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.5 - (show annotations)
Tue Oct 21 13:00:55 2025 UTC (4 months, 3 weeks ago) by sysadm
Branch: MAIN
Changes since 1.4: +25 -18 lines
Content type: text/x-csrc
Set user_list_[rd|rw]_(un)lock() as static
These functions are for internal use only

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 static int user_list_try_rd_lock(int semid, int wait_sec);
58 static int user_list_try_rw_lock(int semid, int wait_sec);
59 static int user_list_rd_unlock(int semid);
60 static int user_list_rw_unlock(int semid);
61 static int user_list_rd_lock(int semid);
62 static int user_list_rw_lock(int semid);
63
64 int user_list_load(MYSQL *db, USER_LIST *p_list)
65 {
66 MYSQL_RES *rs = NULL;
67 MYSQL_ROW row;
68 char sql[SQL_BUFFER_LEN];
69 int ret = 0;
70 int i;
71
72 if (db == NULL || p_list == NULL)
73 {
74 log_error("NULL pointer error\n");
75 return -1;
76 }
77
78 snprintf(sql, sizeof(sql),
79 "SELECT user_list.UID AS UID, username, nickname, gender, gender_pub, life, exp, "
80 "UNIX_TIMESTAMP(signup_dt), UNIX_TIMESTAMP(last_login_dt), UNIX_TIMESTAMP(birthday) "
81 "FROM user_list INNER JOIN user_pubinfo ON user_list.UID = user_pubinfo.UID "
82 "INNER JOIN user_reginfo ON user_list.UID = user_reginfo.UID "
83 "WHERE enable ORDER BY UID");
84
85 if (mysql_query(db, sql) != 0)
86 {
87 log_error("Query user info error: %s\n", mysql_error(db));
88 ret = -1;
89 goto cleanup;
90 }
91
92 if ((rs = mysql_use_result(db)) == NULL)
93 {
94 log_error("Get user info data failed\n");
95 ret = -1;
96 goto cleanup;
97 }
98
99 i = 0;
100 while ((row = mysql_fetch_row(rs)))
101 {
102 p_list->users[i].uid = atoi(row[0]);
103 strncpy(p_list->users[i].username, row[1], sizeof(p_list->users[i].username) - 1);
104 p_list->users[i].username[sizeof(p_list->users[i].username) - 1] = '\0';
105 strncpy(p_list->users[i].nickname, row[2], sizeof(p_list->users[i].nickname) - 1);
106 p_list->users[i].nickname[sizeof(p_list->users[i].nickname) - 1] = '\0';
107 p_list->users[i].gender = row[3][0];
108 p_list->users[i].gender_pub = (int8_t)(row[4] == NULL ? 0 : atoi(row[4]));
109 p_list->users[i].life = (row[5] == NULL ? 0 : atoi(row[5]));
110 p_list->users[i].exp = (row[6] == NULL ? 0 : atoi(row[6]));
111 p_list->users[i].signup_dt = (row[7] == NULL ? 0 : atol(row[7]));
112 p_list->users[i].last_login_dt = (row[8] == NULL ? 0 : atol(row[8]));
113 p_list->users[i].birthday = (row[9] == NULL ? 0 : atol(row[9]));
114
115 i++;
116 if (i >= BBS_max_user_count)
117 {
118 log_error("Too many users, exceed limit %d\n", BBS_max_user_count);
119 break;
120 }
121 }
122 mysql_free_result(rs);
123 rs = NULL;
124
125 p_list->user_count = i;
126
127 #ifdef _DEBUG
128 log_error("Loaded %d users\n", p_list->user_count);
129 #endif
130
131 cleanup:
132 mysql_free_result(rs);
133
134 return ret;
135 }
136
137 int user_list_pool_init(void)
138 {
139 int shmid;
140 int semid;
141 int proj_id;
142 key_t key;
143 size_t size;
144 void *p_shm;
145 union semun arg;
146 int i;
147
148 if (p_user_list_pool != NULL)
149 {
150 log_error("p_user_list_pool already initialized\n");
151 return -1;
152 }
153
154 // Allocate shared memory
155 proj_id = (int)(time(NULL) % getpid());
156 key = ftok(VAR_USER_LIST_SHM, proj_id);
157 if (key == -1)
158 {
159 log_error("ftok(%s %d) error (%d)\n", VAR_USER_LIST_SHM, proj_id, errno);
160 return -2;
161 }
162
163 size = sizeof(USER_LIST_POOL);
164 shmid = shmget(key, size, IPC_CREAT | IPC_EXCL | 0600);
165 if (shmid == -1)
166 {
167 log_error("shmget(size = %d) error (%d)\n", size, errno);
168 return -3;
169 }
170 p_shm = shmat(shmid, NULL, 0);
171 if (p_shm == (void *)-1)
172 {
173 log_error("shmat(shmid=%d) error (%d)\n", shmid, errno);
174 return -3;
175 }
176
177 p_user_list_pool = p_shm;
178 p_user_list_pool->shmid = shmid;
179
180 // Allocate semaphore as user list pool lock
181 size = 2; // r_sem and w_sem
182 semid = semget(key, (int)size, IPC_CREAT | IPC_EXCL | 0600);
183 if (semid == -1)
184 {
185 log_error("semget(user_list_pool_sem, size = %d) error (%d)\n", size, errno);
186 return -3;
187 }
188
189 // Initialize sem value to 0
190 arg.val = 0;
191 for (i = 0; i < size; i++)
192 {
193 if (semctl(semid, i, SETVAL, arg) == -1)
194 {
195 log_error("semctl(user_list_pool_sem, SETVAL) error (%d)\n", errno);
196 return -3;
197 }
198 }
199
200 p_user_list_pool->semid = semid;
201
202 // Set user counts to 0
203 p_user_list_pool->user_list[0].user_count = 0;
204 p_user_list_pool->user_list[1].user_count = 0;
205
206 p_user_list_pool->p_current = &(p_user_list_pool->user_list[0]);
207 p_user_list_pool->p_new = &(p_user_list_pool->user_list[1]);
208
209 return 0;
210 }
211
212 void user_list_pool_cleanup(void)
213 {
214 int shmid;
215
216 if (p_user_list_pool == NULL)
217 {
218 return;
219 }
220
221 shmid = p_user_list_pool->shmid;
222
223 if (semctl(p_user_list_pool->semid, 0, IPC_RMID) == -1)
224 {
225 log_error("semctl(semid = %d, IPC_RMID) error (%d)\n", p_user_list_pool->semid, errno);
226 }
227
228 if (shmdt(p_user_list_pool) == -1)
229 {
230 log_error("shmdt(shmid = %d) error (%d)\n", shmid, errno);
231 }
232
233 if (shmctl(shmid, IPC_RMID, NULL) == -1)
234 {
235 log_error("shmctl(shmid = %d, IPC_RMID) error (%d)\n", shmid, errno);
236 }
237
238 p_user_list_pool = NULL;
239 }
240
241 int set_user_list_pool_shm_readonly(void)
242 {
243 int shmid;
244 void *p_shm;
245
246 if (p_user_list_pool == NULL)
247 {
248 log_error("p_user_list_pool not initialized\n");
249 return -1;
250 }
251
252 shmid = p_user_list_pool->shmid;
253
254 // Remap shared memory in read-only mode
255 p_shm = shmat(shmid, p_user_list_pool, SHM_RDONLY | SHM_REMAP);
256 if (p_shm == (void *)-1)
257 {
258 log_error("shmat(user_list_pool shmid = %d) error (%d)\n", shmid, errno);
259 return -3;
260 }
261
262 p_user_list_pool = p_shm;
263
264 return 0;
265 }
266
267 int detach_user_list_pool_shm(void)
268 {
269 if (p_user_list_pool != NULL && shmdt(p_user_list_pool) == -1)
270 {
271 log_error("shmdt(user_list_pool) error (%d)\n", errno);
272 return -1;
273 }
274
275 p_user_list_pool = NULL;
276
277 return 0;
278 }
279
280 int user_list_pool_reload(void)
281 {
282 MYSQL *db = NULL;
283 USER_LIST *p_tmp;
284
285 if (p_user_list_pool == NULL)
286 {
287 log_error("p_user_list_pool not initialized\n");
288 return -1;
289 }
290
291 db = db_open();
292 if (db == NULL)
293 {
294 log_error("db_open() error: %s\n", mysql_error(db));
295 return -1;
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 return -2;
302 }
303
304 mysql_close(db);
305
306 if (user_list_rw_lock(p_user_list_pool->semid) < 0)
307 {
308 log_error("user_list_rw_lock() error\n");
309 return -3;
310 }
311
312 // Swap p_current and p_new
313 p_tmp = p_user_list_pool->p_current;
314 p_user_list_pool->p_current = p_user_list_pool->p_new;
315 p_user_list_pool->p_new = p_tmp;
316
317 if (user_list_rw_unlock(p_user_list_pool->semid) < 0)
318 {
319 log_error("user_list_rw_unlock() error\n");
320 return -3;
321 }
322
323 return 0;
324 }
325
326 int user_list_try_rd_lock(int semid, int wait_sec)
327 {
328 struct sembuf sops[2];
329 struct timespec timeout;
330 int ret;
331
332 sops[0].sem_num = 1; // w_sem
333 sops[0].sem_op = 0; // wait until unlocked
334 sops[0].sem_flg = 0;
335
336 sops[1].sem_num = 0; // r_sem
337 sops[1].sem_op = 1; // lock
338 sops[1].sem_flg = SEM_UNDO; // undo on terminate
339
340 timeout.tv_sec = wait_sec;
341 timeout.tv_nsec = 0;
342
343 ret = semtimedop(semid, sops, 2, &timeout);
344 if (ret == -1 && errno != EAGAIN && errno != EINTR)
345 {
346 log_error("semtimedop(lock read) error %d\n", errno);
347 }
348
349 return ret;
350 }
351
352 int user_list_try_rw_lock(int semid, int wait_sec)
353 {
354 struct sembuf sops[3];
355 struct timespec timeout;
356 int ret;
357
358 sops[0].sem_num = 1; // w_sem
359 sops[0].sem_op = 0; // wait until unlocked
360 sops[0].sem_flg = 0;
361
362 sops[1].sem_num = 1; // w_sem
363 sops[1].sem_op = 1; // lock
364 sops[1].sem_flg = SEM_UNDO; // undo on terminate
365
366 sops[2].sem_num = 0; // r_sem
367 sops[2].sem_op = 0; // wait until unlocked
368 sops[2].sem_flg = 0;
369
370 timeout.tv_sec = wait_sec;
371 timeout.tv_nsec = 0;
372
373 ret = semtimedop(semid, sops, 3, &timeout);
374 if (ret == -1 && errno != EAGAIN && errno != EINTR)
375 {
376 log_error("semtimedop(lock write) error %d\n", errno);
377 }
378
379 return ret;
380 }
381
382 int user_list_rd_unlock(int semid)
383 {
384 struct sembuf sops[2];
385 int ret;
386
387 sops[0].sem_num = 0; // r_sem
388 sops[0].sem_op = -1; // unlock
389 sops[0].sem_flg = IPC_NOWAIT | SEM_UNDO; // no wait
390
391 ret = semop(semid, sops, 1);
392 if (ret == -1 && errno != EAGAIN && errno != EINTR)
393 {
394 log_error("semop(unlock read) error %d\n", errno);
395 }
396
397 return ret;
398 }
399
400 int user_list_rw_unlock(int semid)
401 {
402 struct sembuf sops[1];
403 int ret;
404
405 sops[0].sem_num = 1; // w_sem
406 sops[0].sem_op = -1; // unlock
407 sops[0].sem_flg = IPC_NOWAIT | SEM_UNDO; // no wait
408
409 ret = semop(semid, sops, 1);
410 if (ret == -1 && errno != EAGAIN && errno != EINTR)
411 {
412 log_error("semop(unlock write) error %d\n", errno);
413 }
414
415 return ret;
416 }
417
418 int user_list_rd_lock(int semid)
419 {
420 int timer = 0;
421 int ret = -1;
422
423 while (!SYS_server_exit)
424 {
425 ret = user_list_try_rd_lock(semid, USER_LIST_TRY_LOCK_WAIT_TIME);
426 if (ret == 0) // success
427 {
428 break;
429 }
430 else if (errno == EAGAIN || errno == EINTR) // retry
431 {
432 timer++;
433 if (timer % USER_LIST_TRY_LOCK_TIMES == 0)
434 {
435 log_error("user_list_try_rd_lock() tried %d times\n", timer);
436 }
437 }
438 else // failed
439 {
440 log_error("user_list_try_rd_lock() failed\n");
441 break;
442 }
443 }
444
445 return ret;
446 }
447
448 int user_list_rw_lock(int semid)
449 {
450 int timer = 0;
451 int ret = -1;
452
453 while (!SYS_server_exit)
454 {
455 ret = user_list_try_rw_lock(semid, USER_LIST_TRY_LOCK_WAIT_TIME);
456 if (ret == 0) // success
457 {
458 break;
459 }
460 else if (errno == EAGAIN || errno == EINTR) // retry
461 {
462 timer++;
463 if (timer % USER_LIST_TRY_LOCK_TIMES == 0)
464 {
465 log_error("user_list_try_rw_lock() tried %d times\n", timer);
466 }
467 }
468 else // failed
469 {
470 log_error("user_list_try_rw_lock() failed\n");
471 break;
472 }
473 }
474
475 return ret;
476 }
477
478 int query_user_list(int page_id, USER_INFO *p_users, int *p_user_count, int *p_page_count)
479 {
480 int ret = 0;
481
482 if (p_users == NULL || p_user_count == NULL || p_page_count == NULL)
483 {
484 log_error("NULL pointer error\n");
485 return -1;
486 }
487
488 // acquire lock of user list
489 if (user_list_rd_lock(p_user_list_pool->semid) < 0)
490 {
491 log_error("user_list_rd_lock() error\n");
492 return -2;
493 }
494
495 if (p_user_list_pool->p_current->user_count == 0)
496 {
497 // empty list
498 ret = 0;
499 goto cleanup;
500 }
501
502 *p_page_count = p_user_list_pool->p_current->user_count / BBS_user_limit_per_page +
503 (p_user_list_pool->p_current->user_count % BBS_user_limit_per_page == 0 ? 0 : 1);
504
505 if (page_id < 0 || page_id >= *p_page_count)
506 {
507 log_error("Invalid page_id = %d, not in range [0, %d)\n", page_id, *p_page_count);
508 ret = -3;
509 goto cleanup;
510 }
511
512 *p_user_count = MIN(BBS_user_limit_per_page,
513 p_user_list_pool->p_current->user_count -
514 page_id * BBS_user_limit_per_page);
515
516 memcpy(p_users,
517 p_user_list_pool->p_current->users + page_id * BBS_user_limit_per_page,
518 sizeof(USER_INFO) * (size_t)(*p_user_count));
519
520 cleanup:
521 // release lock of user list
522 if (user_list_rd_unlock(p_user_list_pool->semid) < 0)
523 {
524 log_error("user_list_rd_unlock() error\n");
525 ret = -1;
526 }
527
528 return ret;
529 }
530
531 int query_user_info(int32_t uid, USER_INFO *p_user)
532 {
533 int left;
534 int right;
535 int mid;
536 int ret = 0;
537
538 if (p_user == NULL)
539 {
540 log_error("NULL pointer error\n");
541 return -1;
542 }
543
544 // acquire lock of user list
545 if (user_list_rd_lock(p_user_list_pool->semid) < 0)
546 {
547 log_error("user_list_rd_lock() error\n");
548 return -2;
549 }
550
551 left = 0;
552 right = p_user_list_pool->p_current->user_count - 1;
553
554 while (left < right)
555 {
556 mid = (left + right) / 2;
557 if (uid < p_user_list_pool->p_current->users[mid].uid)
558 {
559 right = mid;
560 }
561 else if (uid > p_user_list_pool->p_current->users[mid].uid)
562 {
563 left = mid + 1;
564 }
565 else // if (uid == p_user_list_pool->p_current->users[mid].uid)
566 {
567 left = mid;
568 break;
569 }
570 }
571
572 if (uid == p_user_list_pool->p_current->users[left].uid) // Found
573 {
574 *p_user = p_user_list_pool->p_current->users[left];
575 ret = 1;
576 }
577
578 // release lock of user list
579 if (user_list_rd_unlock(p_user_list_pool->semid) < 0)
580 {
581 log_error("user_list_rd_unlock() error\n");
582 ret = -1;
583 }
584
585 return ret;
586 }

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