/* $Id: cache.c,v 1.59 2003/02/27 05:50:41 in2 Exp $ */
#include "bbs.h"
#ifndef __FreeBSD__
union semun {
int val; /* value for SETVAL */
struct semid_ds *buf; /* buffer for IPC_STAT, IPC_SET */
unsigned short int *array; /* array for GETALL, SETALL */
struct seminfo *__buf; /* buffer for IPC_INFO */
};
#endif
/*
* the reason for "safe_sleep" is that we may call sleep during SIGALRM
* handler routine, while SIGALRM is blocked. if we use the original sleep,
* we'll never wake up.
*/
unsigned int
safe_sleep(unsigned int seconds)
{
/* jochang sleep有問題時用 */
sigset_t set, oldset;
sigemptyset(&set);
sigprocmask(SIG_BLOCK, &set, &oldset);
if (sigismember(&oldset, SIGALRM)) {
unsigned long retv;
#if !defined(_BBS_UTIL_C_)
log_usies("SAFE_SLEEP ", "avoid hang");
#endif
sigemptyset(&set);
sigaddset(&set, SIGALRM);
sigprocmask(SIG_UNBLOCK, &set, NULL);
retv = sleep(seconds);
sigprocmask(SIG_BLOCK, &set, NULL);
return retv;
}
return sleep(seconds);
}
#if defined(_BBS_UTIL_C_)
static void
setapath(char *buf, char *boardname)
{
snprintf(buf, "man/boards/%c/%s", boardname[0], boardname);
}
static char *str_dotdir = ".DIR";
static void
setadir(char *buf, char *path)
{
sprintf(buf, "%s/%s", path, str_dotdir);
}
#endif
static void
attach_err(int shmkey, char *name)
{
fprintf(stderr, "[%s error] key = %x\n", name, shmkey);
fprintf(stderr, "errno = %d: %s\n", errno, strerror(errno));
exit(1);
}
void *
attach_shm(int shmkey, int shmsize)
{
void *shmptr;
int shmid;
shmid = shmget(shmkey, shmsize, 0);
if (shmid < 0) {
// SHM should be created by uhash_loader, NOT mbbsd.
attach_err(shmkey, "shmget");
} else {
shmptr = (void *)shmat(shmid, NULL, 0);
if (shmptr == (void *)-1)
attach_err(shmkey, "shmat");
}
return shmptr;
}
#define SEM_FLG 0600 /* semaphore mode */
/* ----------------------------------------------------- */
/* semaphore : for critical section */
/* ----------------------------------------------------- */
void
sem_init(int semkey, int *semid)
{
union semun s;
s.val = 1;
*semid = semget(semkey, 1, 0);
if (*semid == -1) {
*semid = semget(semkey, 1, IPC_CREAT | SEM_FLG);
if (*semid == -1)
attach_err(semkey, "semget");
semctl(*semid, 0, SETVAL, s);
}
}
void
sem_lock(int op, int semid)
{
struct sembuf sops;
sops.sem_num = 0;
sops.sem_flg = SEM_UNDO;
sops.sem_op = op;
if (semop(semid, &sops, 1)) {
perror("semop");
exit(1);
}
}
/* uhash ****************************************** */
/*
* the design is this: we use another stand-alone program to create and load
* data into the hash. (that program could be run in rc-scripts or something
* like that) after loading completes, the stand-alone program sets loaded to
* 1 and exits.
*
* the bbs exits if it can't attach to the shared memory or the hash is not
* loaded yet.
*/
/* attach_uhash should be called before using uhash */
void
attach_SHM(void)
{
SHM = attach_shm(SHM_KEY, sizeof(SHM_t));
if (!SHM->loaded) /* (uhash) assume fresh shared memory is
* zeroed */
exit(1);
if (SHM->Btouchtime == 0)
SHM->Btouchtime = 1;
bcache = SHM->bcache;
GLOBALVAR = SHM->GLOBALVAR;
if (SHM->Ptouchtime == 0)
SHM->Ptouchtime = 1;
if (SHM->Ftouchtime == 0)
SHM->Ftouchtime = 1;
}
void
add_to_uhash(int n, char *id)
{
int *p, h = StringHash(id);
int times;
strlcpy(SHM->userid[n], id, sizeof(SHM->userid[n]));
p = &(SHM->hash_head[h]);
for (times = 0; times < MAX_USERS && *p != -1; ++times)
p = &(SHM->next_in_hash[*p]);
if (times == MAX_USERS)
abort_bbs(0);
SHM->next_in_hash[*p = n] = -1;
}
/*
* note: after remove_from_uhash(), you should add_to_uhash() (likely with a
* different name)
*/
void
remove_from_uhash(int n)
{
int h = StringHash(SHM->userid[n]);
int *p = &(SHM->hash_head[h]);
int times;
for (times = 0; times < MAX_USERS && (*p != -1 && *p != n); ++times)
p = &(SHM->next_in_hash[*p]);
if (times == MAX_USERS)
abort_bbs(0);
if (*p == n)
*p = SHM->next_in_hash[n];
}
int
setumoney(int uid, int money)
{
SHM->money[uid - 1] = money;
passwd_update_money(uid);
return SHM->money[uid - 1];
}
int
deumoney(int uid, int money)
{
if (money < 0 && SHM->money[uid - 1] < -money)
return setumoney(uid, 0);
else
return setumoney(uid, SHM->money[uid - 1] + money);
}
int
demoney(int money)
{
return deumoney(usernum, money);
}
int
moneyof(int uid)
{ /* ptt 改進金錢處理效率 */
return SHM->money[uid - 1];
}
int
searchuser(char *userid)
{
int h, p, times;
h = StringHash(userid);
p = SHM->hash_head[h];
for (times = 0; times < MAX_USERS && p != -1 && p < MAX_USERS ; ++times) {
if (strcasecmp(SHM->userid[p], userid) == 0) {
strcpy(userid, SHM->userid[p]);
return p + 1;
}
p = SHM->next_in_hash[p];
}
return 0;
}
#if !defined(_BBS_UTIL_C_)
int
getuser(char *userid)
{
int uid;
if ((uid = searchuser(userid)))
passwd_query(uid, &xuser);
return uid;
}
char *
getuserid(int num)
{
if (--num >= 0 && num < MAX_USERS)
return ((char *)SHM->userid[num]);
return NULL;
}
void
setuserid(int num, char *userid)
{
if (num > 0 && num <= MAX_USERS) {
if (num > SHM->number)
SHM->number = num;
else
remove_from_uhash(num - 1);
add_to_uhash(num - 1, userid);
}
}
/* 0 ==> 找過期帳號 */
/* 1 ==> 建立新帳號 */
/* should do it by searching "" in the hash */
int
searchnewuser(int mode)
{
register int i, num;
num = SHM->number;
i = 0;
/* 為什麼這邊不用 hash table 去找而要用 linear search? */
while (i < num) {
if (!SHM->userid[i++][0])
return i;
}
if (mode && (num < MAX_USERS))
return num + 1;
return 0;
}
char *
u_namearray(char buf[][IDLEN + 1], int *pnum, char *tag)
{
register char *ptr, tmp;
register int n, total;
char tagbuf[STRLEN];
int ch, ch2, num;
if (*tag == '\0') {
*pnum = SHM->number;
return SHM->userid[0];
}
for (n = 0; tag[n]; n++)
tagbuf[n] = chartoupper(tag[n]);
tagbuf[n] = '\0';
ch = tagbuf[0];
ch2 = ch - 'A' + 'a';
total = SHM->number;
for (n = num = 0; n < total; n++) {
ptr = SHM->userid[n];
tmp = *ptr;
if (tmp == ch || tmp == ch2) {
if (chkstr(tag, tagbuf, ptr))
strcpy(buf[num++], ptr);
}
}
*pnum = num;
return buf[0];
}
#endif
/*-------------------------------------------------------*/
/* .UTMP cache */
/*-------------------------------------------------------*/
#if !defined(_BBS_UTIL_C_)
void
setutmpmode(unsigned int mode)
{
if (currstat != mode)
currutmp->mode = currstat = mode;
/* 追蹤使用者 */
if (HAS_PERM(PERM_LOGUSER)) {
char msg[200];
snprintf(msg, sizeof(msg), "%s setutmpmode to %s(%d) at %s",
cuser.userid, modestring(currutmp, 0), mode, Cdate(&now));
log_user(msg);
}
}
#endif
/* Ptt:這裡加上 hash 觀念找空的 utmp */
void
getnewutmpent(userinfo_t * up)
{
register int i, p;
register userinfo_t *uentp;
for (i = 0, p = StringHash(up->userid) % USHM_SIZE; i < USHM_SIZE; i++, p++) {
if (p == USHM_SIZE)
p = 0;
uentp = &(SHM->uinfo[p]);
if (!(uentp->pid)) {
memcpy(uentp, up, sizeof(userinfo_t));
currutmp = uentp;
return;
}
}
exit(1);
}
int
apply_ulist(int (*fptr) (userinfo_t *))
{
register userinfo_t *uentp;
register int i, state;
for (i = 0; i < USHM_SIZE; i++) {
uentp = &(SHM->uinfo[i]);
if (uentp->pid && (PERM_HIDE(currutmp) || !PERM_HIDE(uentp)))
if ((state = (*fptr) (uentp)))
return state;
}
return 0;
}
userinfo_t *
search_ulist(int uid)
{
return search_ulistn(uid, 1);
}
#if !defined(_BBS_UTIL_C_)
userinfo_t *
search_ulist_pid(int pid)
{
register int i = 0, j, start = 0, end = SHM->UTMPnumber - 1;
register userinfo_t **ulist;
if (end == -1)
return NULL;
ulist = SHM->sorted[SHM->currsorted][7];
for (i = ((start + end) / 2);; i = (start + end) / 2) {
j = pid - ulist[i]->pid;
if (!j) {
return (userinfo_t *) (ulist[i]);
}
if (end == start) {
break;
} else if (i == start) {
i = end;
start = end;
} else if (j > 0)
start = i;
else
end = i;
}
return 0;
}
userinfo_t *
search_ulistn(int uid, int unum)
{
register int i = 0, j, start = 0, end = SHM->UTMPnumber - 1;
register userinfo_t **ulist;
if (end == -1)
return NULL;
ulist = SHM->sorted[SHM->currsorted][6];
for (i = ((start + end) / 2);; i = (start + end) / 2) {
j = uid - ulist[i]->uid;
if (j == 0) {
for (; i > 0 && uid == ulist[i - 1]->uid; --i)
;/* 指到第一筆 */
if ( i + unum - 1 >= 0 &&
(ulist[i + unum - 1] != NULL &&
uid == ulist[i + unum - 1]->uid) )
return (userinfo_t *) (ulist[i + unum - 1]);
break; /* 超過範圍 */
}
if (end == start) {
break;
} else if (i == start) {
i = end;
start = end;
} else if (j > 0)
start = i;
else
end = i;
}
return 0;
}
int
count_logins(int uid, int show)
{
register int i = 0, j, start = 0, end = SHM->UTMPnumber - 1, count;
register userinfo_t **ulist;
if (end == -1)
return 0;
ulist = SHM->sorted[SHM->currsorted][6];
for (i = ((start + end) / 2);; i = (start + end) / 2) {
j = uid - ulist[i]->uid;
if (!j) {
for (; i > 0 && uid == ulist[i - 1]->uid; i--); /* 指到第一筆 */
for (count = 0; uid == ulist[i + count]->uid; count++) {
if (show)
prints("(%d) 目前狀態為: %-17.16s(來自 %s)\n",
count + 1, modestring(ulist[i + count], 0),
ulist[i + count]->from);
}
return count;
}
if (end == start) {
break;
} else if (i == start) {
i = end;
start = end;
} else if (j > 0)
start = i;
else
end = i;
}
return 0;
}
void
purge_utmp(userinfo_t * uentp)
{
logout_friend_online(uentp);
memset(uentp, 0, sizeof(userinfo_t));
SHM->UTMPneedsort = 1;
}
#endif
/*-------------------------------------------------------*/
/* .BOARDS cache */
/*-------------------------------------------------------*/
void
touchdircache(int bid)
{
int *i = (int *)&SHM->dircache[bid - 1][0].filename[0];
*i = 0;
}
void
load_fileheader_cache(int bid, char *direct)
{
int num = getbtotal(bid);
int n = num - DIRCACHESIZE + 1;
if (SHM->Bbusystate != 1 && now - SHM->busystate_b[bid - 1] >= 10) {
SHM->busystate_b[bid - 1] = now;
get_records(direct, SHM->dircache[bid - 1],
sizeof(fileheader_t), n < 1 ? 1 : n, DIRCACHESIZE);
SHM->busystate_b[bid - 1] = 0;
} else {
safe_sleep(1);
}
}
int
get_fileheader_cache(int bid, char *direct, fileheader_t * headers,
int recbase, int nlines)
{
int ret, n, num;
num = getbtotal(bid);
ret = num - recbase + 1,
n = (num - DIRCACHESIZE + 1);
if (SHM->dircache[bid - 1][0].filename[0] == '\0')
load_fileheader_cache(bid, direct);
if (n < 1)
n = recbase - 1;
else
n = recbase - n;
if (n < 0)
n = 0;
if (ret > nlines)
ret = nlines;
memcpy(headers, &(SHM->dircache[bid - 1][n]), sizeof(fileheader_t) * ret);
return ret;
}
static int
cmpboardname(boardheader_t ** brd, boardheader_t ** tmp)
{
return strcasecmp((*brd)->brdname, (*tmp)->brdname);
}
static int
cmpboardclass(boardheader_t ** brd, boardheader_t ** tmp)
{
return (strncmp((*brd)->title, (*tmp)->title, 4) << 8) +
strcasecmp((*brd)->brdname, (*tmp)->brdname);
}
static void
sort_bcache()
{
int i;
/* critical section 不能單獨呼叫 呼叫reload_bcache or reset_board */
for (i = 0; i < SHM->Bnumber; i++) {
SHM->bsorted[1][i] = SHM->bsorted[0][i] = &bcache[i];
}
qsort(SHM->bsorted[0], SHM->Bnumber, sizeof(boardheader_t *),
(QCAST) cmpboardname);
qsort(SHM->bsorted[1], SHM->Bnumber, sizeof(boardheader_t *),
(QCAST) cmpboardclass);
}
static void
reload_bcache()
{
if (SHM->Bbusystate) {
safe_sleep(1);
}
#if !defined(_BBS_UTIL_C_)
else {
int fd, i;
SHM->Bbusystate = 1;
if ((fd = open(fn_board, O_RDONLY)) > 0) {
SHM->Bnumber =
read(fd, bcache, MAX_BOARD * sizeof(boardheader_t)) /
sizeof(boardheader_t);
close(fd);
}
memset(SHM->lastposttime, 0, MAX_BOARD * sizeof(time_t));
/* 等所有 boards 資料更新後再設定 uptime */
SHM->Buptime = SHM->Btouchtime;
log_usies("CACHE", "reload bcache");
sort_bcache();
for (i = 0; i < SHM->Bnumber; ++i) {
bcache[i].firstchild[0] = NULL;
bcache[i].firstchild[1] = NULL;
}
SHM->Bbusystate = 0;
}
#endif
}
void
resolve_boards()
{
while (SHM->Buptime < SHM->Btouchtime) {
reload_bcache();
}
numboards = SHM->Bnumber;
}
void
touch_boards()
{
SHM->Btouchtime = now;
numboards = -1;
resolve_boards();
}
void
addbrd_touchcache()
{
SHM->Bnumber++;
numboards = SHM->Bnumber;
reset_board(numboards);
}
#if !defined(_BBS_UTIL_C_)
void
reset_board(int bid)
{ /* Ptt: 這樣就不用老是touch board了 */
int fd, i, nuser;
boardheader_t *bhdr;
if (--bid < 0)
return;
if (SHM->Bbusystate || now - SHM->busystate_b[bid - 1] < 10) {
safe_sleep(1);
} else {
SHM->busystate_b[bid - 1] = now;
nuser = bcache[bid - 1].nuser;
bhdr = bcache;
bhdr += bid;
if ((fd = open(fn_board, O_RDONLY)) > 0) {
lseek(fd, (off_t) (bid * sizeof(boardheader_t)), SEEK_SET);
read(fd, bhdr, sizeof(boardheader_t));
close(fd);
}
sort_bcache();
for (i = 0; i < SHM->Bnumber; i++) {
bcache[i].firstchild[0] = NULL;
bcache[i].firstchild[1] = NULL;
}
nuser = bcache[bid - 1].nuser;
SHM->busystate_b[bid - 1] = 0;
}
}
int
apply_boards(int (*func) (boardheader_t *))
{
register int i;
register boardheader_t *bhdr;
for (i = 0, bhdr = bcache; i < numboards; i++, bhdr++) {
if (!(bhdr->brdattr & BRD_GROUPBOARD) && Ben_Perm(bhdr) &&
(*func) (bhdr) == QUIT)
return QUIT;
}
return 0;
}
#endif
boardheader_t *
getbcache(int bid)
{ /* Ptt改寫 */
return bcache + bid - 1;
}
int
getbtotal(int bid)
{
return SHM->total[bid - 1];
}
void
setbtotal(int bid)
{
boardheader_t *bh = getbcache(bid);
struct stat st;
char genbuf[256];
int num, fd;
snprintf(genbuf, sizeof(genbuf),
"boards/%c/%s/.DIR", bh->brdname[0], bh->brdname);
if ((fd = open(genbuf, O_RDWR)) < 0)
return; /* .DIR掛了 */
fstat(fd, &st);
num = st.st_size / sizeof(fileheader_t);
SHM->total[bid - 1] = num;
if (num > 0) {
lseek(fd, (off_t) (num - 1) * sizeof(fileheader_t), SEEK_SET);
if (read(fd, genbuf, FNLEN) >= 0) {
SHM->lastposttime[bid - 1] = (time_t) atoi(&genbuf[2]);
}
} else
SHM->lastposttime[bid - 1] = 0;
close(fd);
if (num)
touchdircache(bid);
}
void
touchbpostnum(int bid, int delta)
{
int *total = &SHM->total[bid - 1];
if (*total)
*total += delta;
}
int
getbnum(char *bname)
{
register int i = 0, j, start = 0, end = SHM->Bnumber - 1;
register boardheader_t **bhdr;
bhdr = SHM->bsorted[0];
for (i = ((start + end) / 2);; i = (start + end) / 2) {
if (!(j = strcasecmp(bname, bhdr[i]->brdname)))
return (int)(bhdr[i] - bcache + 1);
if (end == start) {
break;
} else if (i == start) {
i = end;
start = end;
} else if (j > 0)
start = i;
else
end = i;
}
return 0;
}
#if !defined(_BBS_UTIL_C_)
int
haspostperm(char *bname)
{
register int i;
char buf[200];
setbfile(buf, bname, fn_water);
if (belong(buf, cuser.userid))
return 0;
if (!strcasecmp(bname, DEFAULT_BOARD))
return 1;
if (!strcasecmp(bname, "PttLaw"))
return 1;
if (!HAS_PERM(PERM_POST))
return 0;
if (!(i = getbnum(bname)))
return 0;
/* 秘密看板特別處理 */
if (bcache[i - 1].brdattr & BRD_HIDE)
return 1;
i = bcache[i - 1].level;
if (HAS_PERM(PERM_VIOLATELAW) && (i & PERM_VIOLATELAW))
return 1;
else if (HAS_PERM(PERM_VIOLATELAW))
return 0;
return HAS_PERM(i & ~PERM_POST);
}
#endif
/*-------------------------------------------------------*/
/* PTT cache */
/*-------------------------------------------------------*/
/* cachefor 動態看板 */
void
reload_pttcache()
{
if (SHM->Pbusystate)
safe_sleep(1);
else { /* jochang: temporary workaround */
fileheader_t item, subitem;
char pbuf[256], buf[256], *chr;
FILE *fp, *fp1, *fp2;
int id, section = 0;
SHM->Pbusystate = 1;
SHM->max_film = 0;
bzero(SHM->notes, sizeof(SHM->notes));
setapath(pbuf, "Note");
setadir(buf, pbuf);
id = 0;
if ((fp = fopen(buf, "r"))) {
while (fread(&item, sizeof(item), 1, fp)) {
if (item.title[3] == '<' && item.title[8] == '>') {
snprintf(buf, sizeof(buf), "%s/%s", pbuf, item.filename);
setadir(buf, buf);
if (!(fp1 = fopen(buf, "r")))
continue;
SHM->next_refresh[section] = SHM->n_notes[section] = id;
section++;
while (fread(&subitem, sizeof(subitem), 1, fp1)) {
snprintf(buf, sizeof(buf),
"%s/%s/%s", pbuf, item.filename,
subitem.filename);
if (!(fp2 = fopen(buf, "r")))
continue;
fread(SHM->notes[id], sizeof(char), 200 * 11, fp2);
SHM->notes[id][200 * 11 - 1] = 0;
id++;
fclose(fp2);
if (id >= MAX_MOVIE)
break;
}
fclose(fp1);
if (id >= MAX_MOVIE || section >= MAX_MOVIE_SECTION)
break;
}
}
fclose(fp);
}
SHM->next_refresh[section] = -1;
SHM->n_notes[section] = SHM->max_film = id - 1;
SHM->max_history = SHM->max_film - 2;
if (SHM->max_history > MAX_HISTORY - 1)
SHM->max_history = MAX_HISTORY - 1;
if (SHM->max_history < 0)
SHM->max_history = 0;
fp = fopen("etc/today_is", "r");
if (fp) {
fgets(SHM->today_is, 15, fp);
if ((chr = strchr(SHM->today_is, '\n')))
*chr = 0;
SHM->today_is[15] = 0;
fclose(fp);
}
/* 等所有資料更新後再設定 uptime */
SHM->Puptime = SHM->Ptouchtime;
#if !defined(_BBS_UTIL_C_)
log_usies("CACHE", "reload pttcache");
#endif
SHM->Pbusystate = 0;
}
}
void
resolve_garbage()
{
int count = 0;
while (SHM->Puptime < SHM->Ptouchtime) { /* 不用while等 */
reload_pttcache();
if (count++ > 10 && SHM->Pbusystate) {
/*
* Ptt: 這邊會有問題 load超過10 秒會所有進loop的process tate = 0
* 這樣會所有prcosee都會在load 動態看板 會造成load大增
* 但沒有用這個function的話 萬一load passwd檔的process死了
* 又沒有人把他 解開 同樣的問題發生在reload passwd
*/
SHM->Pbusystate = 0;
#ifndef _BBS_UTIL_C_
log_usies("CACHE", "refork Ptt dead lock");
#endif
}
}
}
/*-------------------------------------------------------*/
/* PTT's cache */
/*-------------------------------------------------------*/
/* cachefor from host 與最多上線人數 */
static void
reload_fcache()
{
if (SHM->Fbusystate)
safe_sleep(1);
else {
FILE *fp;
SHM->Fbusystate = 1;
bzero(SHM->domain, sizeof(SHM->domain));
if ((fp = fopen("etc/domain_name_query", "r"))) {
char buf[256], *po;
SHM->top = 0;
while (fgets(buf, sizeof(buf), fp)) {
if (buf[0] && buf[0] != '#' && buf[0] != ' ' &&
buf[0] != '\n') {
sscanf(buf, "%s", SHM->domain[SHM->top]);
po = buf + strlen(SHM->domain[SHM->top]);
while (*po == ' ')
po++;
strncpy(SHM->replace[SHM->top], po, 49);
SHM->replace[SHM->top]
[strlen(SHM->replace[SHM->top]) - 1] = 0;
(SHM->top)++;
if (SHM->top == MAX_FROM)
break;
}
}
fclose(fp);
}
SHM->max_user = 0;
/* 等所有資料更新後再設定 uptime */
SHM->Fuptime = SHM->Ftouchtime;
#if !defined(_BBS_UTIL_C_)
log_usies("CACHE", "reload fcache");
#endif
SHM->Fbusystate = 0;
}
}
void
resolve_fcache()
{
while (SHM->Fuptime < SHM->Ftouchtime)
reload_fcache();
}
void
hbflreload(int bid)
{
int hbfl[MAX_FRIEND + 1], i, num, uid;
char buf[128];
FILE *fp;
memset(hbfl, 0, sizeof(hbfl));
setbfile(buf, bcache[bid - 1].brdname, fn_visable);
if ((fp = fopen(buf, "r")) != NULL) {
for (num = 1; num <= MAX_FRIEND; ++num) {
if (fgets(buf, sizeof(buf), fp) == NULL)
break;
for (i = 0; buf[i] != 0; ++i)
if (buf[i] == ' ') {
buf[i] = 0;
break;
}
if (strcasecmp("guest", buf) == 0 ||
(uid = searchuser(buf)) == 0) {
--num;
continue;
}
hbfl[num] = uid;
}
fclose(fp);
}
hbfl[0] = now;
memcpy(SHM->hbfl[bid], hbfl, sizeof(hbfl));
}
int
hbflcheck(int bid, int uid)
{
int i;
if (SHM->hbfl[bid][0] < login_start_time - HBFLexpire)
hbflreload(bid);
for (i = 1; SHM->hbfl[bid][i] != 0 && i <= MAX_FRIEND; ++i) {
if (SHM->hbfl[bid][i] == uid)
return 0;
}
return 1;
}