Files
undernet-cservice/Sources/dbio.c
2015-01-25 18:26:44 -06:00

756 lines
18 KiB
C

/* @(#)$Id: dbio.c,v 1.12 1998/01/02 18:30:08 seks Exp $ */
/* Undernet Channel Service (X)
* Copyright (C) 1995-2002 Robin Thellend
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* The author can be contact by email at <csfeedback@robin.pfft.net>
*
* Please note that this software is unsupported and mostly
* obsolete. It was replaced by GNUworld/CMaster. See
* http://gnuworld.sourceforge.net/ for more information.
*/
#include "h.h"
char *make_dbfname(char *channel)
{
static char fname[600];
char tmp[600];
register char *ptr;
strcpy(tmp, channel);
for (ptr = tmp; *ptr; ptr++)
{
if (*ptr == '/')
*ptr = ' ';
else
*ptr = tolower(*ptr);
}
sprintf(fname, "db/channels/%04X/%s", ul_hash(channel), tmp);
return fname;
}
/* db_query()
* This is used to *retreive* information from the database.
* The arguments are:
* channel := channel name (null terminated string)
* type := query type, either of:
* DBGETNICK (Get entry by nick)
* DBGET1STUH (Get first matching userhost)
* DBGETALLUH (Get all matching userhosts)
* DBCOUNTUH (Count the number of matching userhosts)
* ...
* info := Either a nick or a userhost depending on type
* action := value passed to the callback function
* hook1 := pointer passed to the callback function
* hook2 := pointer passed to the callback function
* callback := callback function
*
* Since the database is accessed asychronously, 'hook' should not point to
* a structure that can be free()'d by a function other than callback,
* e.g. a pointer to a luser structure.
*/
int db_fetch(char *channel, unsigned int type, char *info, char *passwd,
int action, void *hook1, void *hook2, DBCALLBACK(callback))
{
dbquery *new;
int fd;
if ((fd = open(make_dbfname(channel), O_RDONLY | O_NONBLOCK)) < 0)
{
callback(&fd, (off_t) 0, action, hook1, hook2, NULL, 0);
return -1; /* caller can check errno to see want happened */
}
new = (dbquery *) malloc(sizeof(dbquery));
new->fd = fd;
new->offset = (off_t) 0;
new->time = now;
new->type = type;
new->action = action;
new->count = 0;
new->callback = callback;
new->buf = NULL;
new->hook1 = hook1;
new->hook2 = hook2;
strcpy(new->info, info);
strncpy(new->channel, channel, 79);
new->channel[79] = '\0';
if (passwd)
strcpy(new->passwd, passwd);
else
new->passwd[0] = '\0';
new->next = DBQuery;
DBQuery = new;
return 0;
}
/* fix_db()
* Remove db entries that are not in the right file.
*/
static void fix_db(char *channel, off_t offset)
{
register RegUser *reg;
register int idx;
idx = ul_hash(channel);
/* check if already taken care of */
reg = UserList[idx];
while (reg != NULL && (reg->offset != offset || strcasecmp(reg->channel, channel)))
reg = reg->next;
if (reg) /* already there.. disregard */
return;
reg = (RegUser *) MALLOC(sizeof(RegUser));
memset(reg, 0, sizeof(RegUser));
reg->realname = (char *)MALLOC(6);
strcpy(reg->realname, "!DEL!");
reg->passwd = (char *)MALLOC(1);
*reg->passwd = '\0';
reg->match = (char *)MALLOC(6);
strcpy(reg->match, "!DEL!");
reg->channel = (char *)MALLOC(strlen(channel) + 1);
strcpy(reg->channel, channel);
reg->modif = (char *)MALLOC(1);
*reg->modif = '\0';
reg->modified = 1;
reg->offset = offset;
reg->next = UserList[idx];
UserList[idx] = reg;
}
/* read_db()
* This should be called from the select() loop. Data is read sequentially
* and the query is processed according to 'type'.
*/
void read_db(dbquery * query)
{
struct dbuser buffer[11];
int size, status, end = 0;
size = read(query->fd, buffer, 10 * sizeof(dbuser));
if (size <= 0)
{
if (errno != EAGAIN && errno != EWOULDBLOCK)
{
close(query->fd);
query->fd = -1;
}
return;
}
copy_to_buffer(&query->buf, (char *)buffer, size);
while (!end && look_in_buffer(&query->buf, (char *)buffer, '\0', sizeof(dbuser))
== sizeof(dbuser))
{
if (buffer[0].header[0] == 0xFF && buffer[0].header[1] == 0xFF &&
buffer[0].footer[0] == 0xFF && buffer[0].footer[1] == 0xFF)
{
status = 1; /* block is used */
}
else if (buffer[0].header[0] == 0x00 && buffer[0].header[1] == 0x00 &&
buffer[0].footer[0] == 0x00 && buffer[0].footer[1] == 0x00)
{
status = 0; /* block is free */
}
else
{
status = -1; /* block has been written to while reading it */
}
if (status == 1 && strcasecmp(query->channel, buffer[0].channel))
{
fix_db(query->channel, query->offset);
skip_char_in_buffer(&query->buf, sizeof(dbuser));
query->offset += sizeof(dbuser);
continue;
}
#ifdef DEBUG
printf("hdr: %X%X nick: %s match: %s passwd: %s channel: %s "
"modif: %s access: %d flags: %ld susp: %ld last: %ld ftr: %X%X"
"sta: %d\n",
buffer[0].header[0], buffer[0].header[1], buffer[0].nick, buffer[0].match, buffer[0].passwd,
buffer[0].channel, buffer[0].modif, buffer[0].access, buffer[0].flags, buffer[0].suspend,
buffer[0].lastseen, buffer[0].footer[0], buffer[0].footer[1], status);
#endif
switch (query->type)
{
case DBGETNICK:
if (status == 1 && !strcasecmp(buffer[0].nick, query->info))
{
close(query->fd);
query->fd = -1;
query->count++;
query->callback(&query->fd, query->offset, query->action, query->hook1,
query->hook2, buffer, query->count);
end = 1;
}
break;
case DBGET1STUH:
if (status == 1 && match(query->info, buffer[0].match))
{
close(query->fd);
query->fd = -1;
query->count++;
query->callback(&query->fd, query->offset, query->action, query->hook1,
query->hook2, buffer, query->count);
end = 1;
}
break;
case DBGETALLUH:
if (status == 1 && match(query->info, buffer[0].match))
{
query->count++;
query->callback(&query->fd, query->offset, query->action, query->hook1,
query->hook2, buffer, query->count);
}
break;
case DBGETUHPASS:
if (status == 1 && match(query->info, buffer[0].match) &&
!strcmp(query->passwd, buffer[0].passwd))
{
close(query->fd);
query->fd = -1;
query->count++;
query->callback(&query->fd, query->offset, query->action, query->hook1,
query->hook2, buffer, query->count);
end = 1;
}
break;
case DBCOUNTUH:
if (status == 1 && match(query->info, buffer[0].match))
{
query->count++;
}
break;
case DBGET1STCMP:
if (status == 1 && (!*query->info || compare(query->info, buffer[0].match)))
{
close(query->fd);
query->fd = -1;
query->count++;
query->callback(&query->fd, query->offset, query->action, query->hook1,
query->hook2, buffer, query->count);
end = 1;
}
break;
case DBGETALLCMP:
if (status == 1 && (!*query->info || compare(query->info, buffer[0].match)))
{
query->count++;
query->callback(&query->fd, query->offset, query->action, query->hook1,
query->hook2, buffer, query->count);
}
break;
case DBCNTCMP:
if (status == 1 && (!*query->info || compare(query->info, buffer[0].match)))
{
query->count++;
}
break;
case DBGET1STFREE:
if (status == 0)
{
close(query->fd);
query->fd = -1;
query->count++;
query->callback(&query->fd, query->offset, query->action, query->hook1,
query->hook2, buffer, query->count);
end = 1;
}
break;
}
skip_char_in_buffer(&query->buf, sizeof(dbuser));
query->offset += sizeof(dbuser);
if (query->fd < 0)
end = 1;
}
}
void end_db_read(dbquery * query)
{
zap_buffer(&query->buf);
query->callback(&query->fd, query->offset, query->action, query->hook1,
query->hook2, NULL, query->count);
}
void make_dbuser(RegUser * reg, dbuser * dbu)
{
memset(dbu, 0, sizeof(dbuser));
if (reg->access != 0)
{
strncpy(dbu->nick, reg->realname, 79);
dbu->nick[79] = '\0';;
strncpy(dbu->match, reg->match, 79);
dbu->match[79] = '\0';
strncpy(dbu->channel, reg->channel, 49);
dbu->channel[49] = '\0';
strncpy(dbu->passwd, reg->passwd, 19);
dbu->passwd[19] = '\0';
strncpy(dbu->modif, reg->modif, 79);
dbu->modif[79] = '\0';
dbu->access = reg->access;
dbu->flags = reg->flags;
dbu->suspend = reg->suspend;
dbu->lastseen = reg->lastseen;
dbu->header[0] = 0xFF;
dbu->header[1] = 0xFF;
dbu->footer[0] = 0xFF;
dbu->footer[1] = 0xFF;
}
}
void sync_next_channel(void)
{
register syncchan *tmp;
if ((tmp = SyncChan) != NULL)
{
SyncChan = SyncChan->next;
db_sync(tmp->name);
free(tmp);
}
}
static void set_sync(dbsync * sync)
{
struct dbuser dbu;
if (*sync->reg == NULL)
{
close(sync->fd);
sync->fd = -1;
return;
}
if ((*sync->reg)->access == 0) /* delete */
{
make_dbuser(*sync->reg, &dbu);
zap_buffer(&sync->buf);
copy_to_buffer(&sync->buf, (char *)&dbu, sizeof(dbuser));
sync->type = SYNC_DELETE;
sync->status = SYNC_PENDWRITE;
sync->offset = (*sync->reg)->offset;
lseek(sync->fd, (*sync->reg)->offset, SEEK_SET);
}
else if ((*sync->reg)->offset != (off_t) - 1) /* update */
{
make_dbuser(*sync->reg, &dbu);
zap_buffer(&sync->buf);
copy_to_buffer(&sync->buf, (char *)&dbu, sizeof(dbuser));
sync->type = SYNC_UPDATE;
sync->status = SYNC_PENDWRITE;
sync->offset = (*sync->reg)->offset;
lseek(sync->fd, (*sync->reg)->offset, SEEK_SET);
}
else
/* add */
{
sync->type = SYNC_ADD;
sync->status = SYNC_SEEKFREE;
sync->offset = (off_t) 0;
lseek(sync->fd, 0L, SEEK_SET);
}
}
static void sync_next(dbsync * sync, char *channel)
{
register RegUser **reg = sync->reg;
if (*reg == NULL)
{
close(sync->fd);
sync->fd = -1;
return;
}
while (*reg != NULL && ((*reg)->modified == 0 || (*reg)->access == 1000 ||
strcasecmp((*reg)->channel, channel)))
{
if ((*reg)->inuse == 0 && (*reg)->lastused + CACHE_TIMEOUT < now)
free_user(reg);
else
reg = &(*reg)->next;
}
sync->time = now;
sync->reg = reg;
set_sync(sync);
}
void db_sync(char *channel)
{
register RegUser **reg, *tmp;
register char *ch;
register dbsync *sync;
struct stat st;
int fd;
#ifdef DEBUG
printf("SYNC: %s\n", channel);
#endif
if (DBSync != NULL && (DBSync->fd != -1 || DBSync->next != NULL))
{
log("ERROR: simultaneous syncs??");
return;
}
ch = make_dbfname(channel);
/* IF file does not exist.. remove all deletes and mark
* all other entries as new.
*/
if (stat(ch, &st) < 0)
{
/* remove all deletes */
reg = &UserList[ul_hash(channel)];
while (*reg)
{
(*reg)->offset = (off_t) - 1;
if ((*reg)->access == 0 && (*reg)->inuse == 0 &&
!strcasecmp((*reg)->channel, channel))
{
tmp = *reg;
*reg = (*reg)->next;
free_user(reg);
}
else
reg = &(*reg)->next;
}
fd = open(ch, O_RDWR | O_CREAT | O_EXCL | O_NONBLOCK, 0600);
}
else
{
fd = open(ch, O_RDWR | O_NONBLOCK);
}
/* create new sync struct
*/
sync = (dbsync *) malloc(sizeof(dbsync));
sync->fd = fd;
sync->reg = &UserList[ul_hash(channel)];
sync->buf = NULL;
sync->next = DBSync;
DBSync = sync;
sync_next(sync, channel);
}
void db_sync_ready(dbsync * sync)
{
struct dbuser buffer[10];
register RegUser **reg = sync->reg;
int size;
#ifdef DEBUG
printf("SYNC_READY: status= %d\n", sync->status);
#endif
if (sync->status == SYNC_PENDWRITE)
{
size = look_in_buffer(&sync->buf, (char *)buffer, '\0', sizeof(dbuser));
if (size == 0)
zap_buffer(&sync->buf);
if (sync->buf == NULL)
{
if ((*reg)->access == 0 && (*reg)->inuse == 0)
free_user(reg);
else
(*reg)->modified = 0;
sync_next(sync, (*reg)->channel);
return;
}
#ifdef DEBUG
printf("PENDWRITE: %d bytes to write...", size);
#endif
size = write(sync->fd, buffer, size);
#ifdef DEBUG
printf("%d written\n", size);
#endif
if (size <= 0)
{
if (errno != EWOULDBLOCK && errno != EAGAIN)
{
close(sync->fd);
sync->fd = -1;
}
return;
}
skip_char_in_buffer(&sync->buf, size);
}
else if (sync->status == SYNC_SEEKFREE)
{
size = read(sync->fd, buffer, 10 * sizeof(dbuser));
if (size <= 0)
{
if (errno != EAGAIN && errno != EWOULDBLOCK)
{
make_dbuser(*sync->reg, buffer);
zap_buffer(&sync->buf);
copy_to_buffer(&sync->buf, (char *)buffer, sizeof(dbuser));
sync->status = SYNC_PENDWRITE;
lseek(sync->fd, 0L, SEEK_END);
}
return;
}
copy_to_buffer(&sync->buf, (char *)buffer, size);
while (look_in_buffer(&sync->buf, (char *)buffer, '\0', sizeof(dbuser))
== sizeof(dbuser))
{
if (buffer[0].header[0] == 0x00 && buffer[0].header[1] == 0x00 &&
buffer[0].footer[0] == 0x00 && buffer[0].footer[1] == 0x00)
{
make_dbuser(*sync->reg, buffer);
zap_buffer(&sync->buf);
copy_to_buffer(&sync->buf, (char *)buffer, sizeof(dbuser));
sync->status = SYNC_PENDWRITE;
(*sync->reg)->offset = sync->offset;
lseek(sync->fd, sync->offset, SEEK_SET);
return;
}
skip_char_in_buffer(&sync->buf, sizeof(dbuser));
sync->offset += sizeof(dbuser);
}
}
}
void end_db_sync(dbsync * sync)
{
#ifdef DEBUG
printf("SYNC_END\n");
#endif
sync_next_channel();
return;
}
void cold_save_one(RegUser * reg)
{
struct stat st;
dbuser dbu;
char *fname;
register int fd;
off_t off;
fname = make_dbfname(reg->channel);
if (stat(fname, &st) < 0) /* file doesn't exist -- don't save delete */
{
if (reg->access == 0)
return;
fd = open(fname, O_RDWR | O_CREAT, 0600);
}
else
fd = open(fname, O_RDWR | O_EXCL);
if (fd < 0) /* hmmm? */
return;
if (reg->offset == (off_t) - 1) /* new ==> seek for empty slot */
{
lseek(fd, 0L, SEEK_SET);
off = (off_t) 0;
while (read(fd, &dbu, sizeof(dbuser)) == sizeof(dbuser))
{
if (dbu.header[0] == 0x00 && dbu.header[1] == 0x00 &&
dbu.footer[0] == 0x00 && dbu.footer[1] == 0x00)
break; /* found empty slot */
off += sizeof(dbuser);
}
}
else
off = reg->offset;
lseek(fd, off, SEEK_SET); /* go to write position */
if (reg->access != 0)
make_dbuser(reg, &dbu);
else
memset(&dbu, 0, sizeof(dbuser));
write(fd, &dbu, sizeof(dbuser));
if (reg->access != 0)
reg->offset = off;
else
reg->offset = (off_t) - 1;
reg->modified = 0;
close(fd);
}
void do_cold_sync_slice(void)
{
register RegUser **reg;
if (DB_Save_Status < 0)
return;
reg = &UserList[DB_Save_Status];
while (*reg != NULL)
{
if ((*reg)->modified && (*reg)->access < 1000)
cold_save_one(*reg);
if ((*reg)->inuse == 0 && (*reg)->access < 1000 &&
((*reg)->access == 0 || (*reg)->lastused + CACHE_TIMEOUT < now))
free_user(reg);
else
reg = &(*reg)->next;
}
if (++DB_Save_Status == 1000)
{
DB_Save_Status = -1;
if (*DB_Save_Nick)
notice(DB_Save_Nick, "Userlist sync complete!");
}
}
void do_cold_sync(void)
{
register dbsync *sync;
register syncchan *schan;
register RegUser **reg;
register int i;
char buffer[200];
/* SET AWAY MESSAGE */
sprintf(buffer, ":%s AWAY :Busy saving precious user list\n", mynick);
sendtoserv(buffer);
dumpbuff();
/* First, cancel *all* sync requests */
while ((sync = DBSync) != NULL)
{
DBSync = DBSync->next;
if (sync->fd >= 0)
close(sync->fd);
zap_buffer(&sync->buf);
free(sync);
}
/* Also, clear *all* pending syncs */
while ((schan = SyncChan) != NULL)
{
SyncChan = SyncChan->next;
free(schan);
}
/* Now, go thru *all* the user entries in memory and save them
* to their respective files.
*/
for (i = 0; i < 1000; i++)
{
reg = &UserList[i];
while (*reg != NULL)
{
if ((*reg)->modified && (*reg)->access < 1000)
cold_save_one(*reg);
if ((*reg)->inuse == 0 && (*reg)->access < 1000 &&
((*reg)->access == 0 || (*reg)->lastused + CACHE_TIMEOUT < now))
free_user(reg);
else
reg = &(*reg)->next;
}
}
sprintf(buffer, ":%s AWAY\n", mynick);
sendtoserv(buffer);
}
#ifdef DEBUG
void db_test_callback(int *fd, off_t off, int action, void *hook1, void *hook2,
dbuser * dbu, int count)
{
char buffer[512];
sprintf(buffer, "off: %ld act: %d hook=%p cnt: %d", off, action, hook1, count);
notice((char *)hook1, buffer);
if (dbu)
{
sprintf(buffer, "hdr: %X%X nick: %s match: %s passwd: %s channel: %s "
"modif: %s access: %d flags: %ld susp: %ld last: %ld ftr: %X%X",
dbu->header[0], dbu->header[1], dbu->nick, dbu->match, dbu->passwd,
dbu->channel, dbu->modif, dbu->access, dbu->flags, dbu->suspend,
dbu->lastseen, dbu->footer[0], dbu->footer[1]);
notice((char *)hook1, buffer);
}
else
{
notice((char *)hook1, "End.");
free(hook1);
}
}
void db_test(char *source, char *chan, char *args)
{
char channel[80], type[80], info[80], *hook;
GetWord(0, args, channel);
GetWord(1, args, type);
GetWord(2, args, info);
hook = (char *)malloc(strlen(source) + 1);
strcpy(hook, source);
if (!strcmp(type, "nick"))
db_fetch(channel, DBGETNICK, info, NULL, 0, hook, NULL, db_test_callback);
else if (!strcmp(type, "alluh"))
db_fetch(channel, DBGETALLUH, info, NULL, 0, hook, NULL, db_test_callback);
else if (!strcmp(type, "1stuh"))
db_fetch(channel, DBGET1STUH, info, NULL, 0, hook, NULL, db_test_callback);
else if (!strcmp(type, "count"))
db_fetch(channel, DBCOUNTUH, info, NULL, 0, hook, NULL, db_test_callback);
else if (!strcmp(type, "1stcmp"))
db_fetch(channel, DBGET1STCMP, info, NULL, 0, hook, NULL, db_test_callback);
else if (!strcmp(type, "allcmp"))
db_fetch(channel, DBGETALLCMP, info, NULL, 0, hook, NULL, db_test_callback);
else if (!strcmp(type, "cntcmp"))
db_fetch(channel, DBCNTCMP, info, NULL, 0, hook, NULL, db_test_callback);
else if (!strcmp(type, "sync"))
db_sync(channel);
}
#endif