
2001-11-01 <NotZed@Ximian.com> * ibex_block.c (ibex_open): Change to use pthread_mutex directly. (ibex_close): Same. * ibex_internal.h: Changed to use pthread mutexes. Fixes #14218. svn path=/trunk/; revision=14565
650 lines
14 KiB
C
650 lines
14 KiB
C
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <glib.h>
|
|
|
|
#include <stdio.h>
|
|
#include <gal/unicode/gunicode.h>
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#ifdef HAVE_ALLOCA_H
|
|
#include <alloca.h>
|
|
#endif
|
|
|
|
#include "ibex_internal.h"
|
|
|
|
#define d(x)
|
|
#define o(x)
|
|
|
|
static void ibex_reset(ibex *ib);
|
|
static int close_backend(ibex *ib);
|
|
|
|
static struct _list ibex_list = { (struct _listnode *)&ibex_list.tail, 0, (struct _listnode *)&ibex_list.head };
|
|
static int ibex_open_init = 0;
|
|
static int ibex_open_threshold = IBEX_OPEN_THRESHOLD;
|
|
|
|
#ifdef ENABLE_THREADS
|
|
#include <pthread.h>
|
|
static pthread_mutex_t ibex_list_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
static int ibex_opened; /* count of actually opened ibexe's */
|
|
#define IBEX_LIST_LOCK(ib) (pthread_mutex_lock(&ibex_list_lock))
|
|
#define IBEX_LIST_UNLOCK(ib) (pthread_mutex_unlock(&ibex_list_lock))
|
|
#else
|
|
#define IBEX_LIST_LOCK(ib)
|
|
#define IBEX_LIST_UNLOCK(ib)
|
|
#endif
|
|
|
|
/* check we are open properly */
|
|
|
|
/* TODO: return errors? */
|
|
static void ibex_use(ibex *ib)
|
|
{
|
|
ibex *wb, *wn;
|
|
|
|
/* always lock list then ibex */
|
|
IBEX_LIST_LOCK(ib);
|
|
|
|
if (ib->blocks == NULL) {
|
|
o(printf("Delayed opening ibex '%s', total = %d\n", ib->name, ibex_opened+1));
|
|
|
|
ib->blocks = ibex_block_cache_open(ib->name, ib->flags, ib->mode);
|
|
if (ib->blocks) {
|
|
/* FIXME: the blockcache or the wordindex needs to manage the other one */
|
|
ib->words = ib->blocks->words;
|
|
} else {
|
|
ib->words = NULL;
|
|
g_warning("ibex_use:open(): Error occured?: %s\n", strerror(errno));
|
|
}
|
|
if (ib->blocks != NULL)
|
|
ibex_opened++;
|
|
}
|
|
|
|
ib->usecount++;
|
|
|
|
/* this makes an 'lru' cache of used files */
|
|
ibex_list_remove((struct _listnode *)ib);
|
|
ibex_list_addtail(&ibex_list, (struct _listnode *)ib);
|
|
|
|
/* check env variable override for open threshold */
|
|
if (!ibex_open_init) {
|
|
char *limit;
|
|
|
|
ibex_open_init = TRUE;
|
|
limit = getenv("IBEX_OPEN_THRESHOLD");
|
|
if (limit) {
|
|
ibex_open_threshold = atoi(limit);
|
|
if (ibex_open_threshold < IBEX_OPEN_THRESHOLD)
|
|
ibex_open_threshold = IBEX_OPEN_THRESHOLD;
|
|
}
|
|
}
|
|
|
|
/* check for other ibex's we can close now to not over-use fd's.
|
|
we can't do this first for locking issues */
|
|
wb = (ibex *)ibex_list.head;
|
|
wn = wb->next;
|
|
while (wn && ibex_opened > ibex_open_threshold) {
|
|
if (wb != ib && IBEX_TRYLOCK(wb) == 0) {
|
|
if (wb->usecount == 0 && wb->blocks != NULL) {
|
|
o(printf("Forcing close of obex '%s', total = %d\n", wb->name, ibex_opened-1));
|
|
close_backend(wb);
|
|
ibex_opened--;
|
|
}
|
|
IBEX_UNLOCK(wb);
|
|
}
|
|
wb = wn;
|
|
wn = wn->next;
|
|
}
|
|
|
|
IBEX_LIST_UNLOCK(ib);
|
|
}
|
|
|
|
static void ibex_unuse(ibex *ib)
|
|
{
|
|
ib->usecount--;
|
|
}
|
|
|
|
static signed char utf8_trans[] = {
|
|
'A', 'A', 'A', 'A', 'A', 'A', -1, 'C', 'E', 'E', 'E', 'E', 'I', 'I',
|
|
'I', 'I', -2, 'N', 'O', 'O', 'O', 'O', 'O', '*', 'O', 'U', 'U', 'U',
|
|
'U', 'Y', -3, -4, 'a', 'a', 'a', 'a', 'a', 'a', -5, 'c', 'e', 'e',
|
|
'e', 'e', 'i', 'i', 'i', 'i', -6, 'n', 'o', 'o', 'o', 'o', 'o', '/',
|
|
'o', 'u', 'u', 'u', 'u', 'y', -7, 'y', 'A', 'a', 'A', 'a', 'A', 'a',
|
|
'C', 'c', 'C', 'c', 'C', 'c', 'C', 'c', 'D', 'd', 'D', 'd', 'E', 'e',
|
|
'E', 'e', 'E', 'e', 'E', 'e', 'E', 'e', 'G', 'g', 'G', 'g', 'G', 'g',
|
|
'G', 'g', 'H', 'h', 'H', 'h', 'I', 'i', 'I', 'i', 'I', 'i', 'I', 'i',
|
|
'I', 'i', -8, -9, 'J', 'j', 'K', 'k', 'k', 'L', 'l', 'L', 'l', 'L',
|
|
'l', 'L', 'l', 'L', 'l', 'N', 'n', 'N', 'n', 'N', 'n', 'n', -10, -11,
|
|
'O', 'o', 'O', 'o', 'O', 'o', -12, -13, 'R', 'r', 'R', 'r', 'R', 'r',
|
|
'S', 'r', 'S', 's', 'S', 's', 'S', 's', 'T', 't', 'T', 't', 'T', 't',
|
|
'U', 'u', 'U', 'u', 'U', 'u', 'U', 'u', 'U', 'u', 'U', 'u', 'W', 'w',
|
|
'Y', 'y', 'Y', 'Z', 'z', 'Z', 'z', 'Z', 'z', 's'
|
|
};
|
|
|
|
static char *utf8_long_trans[] = {
|
|
"AE", "TH", "TH", "ss", "ae", "th", "th", "IJ", "ij",
|
|
"NG", "ng", "OE", "oe"
|
|
};
|
|
|
|
/* This is a bit weird. It takes pointers to the start and end (actually
|
|
* just past the end) of a UTF-8-encoded word, and a buffer at least 1
|
|
* byte longer than the length of the word. It copies the word into the
|
|
* buffer in all lowercase without accents, and splits up ligatures.
|
|
* (Since any ligature would be a multi-byte character in UTF-8, splitting
|
|
* them into two US-ASCII characters won't overrun the buffer.)
|
|
*
|
|
* It is not safe to call this routine with bad UTF-8.
|
|
*/
|
|
static void
|
|
ibex_normalise_word(char *start, char *end, char *buf)
|
|
{
|
|
unsigned char *s, *d;
|
|
gunichar uc;
|
|
|
|
s = (unsigned char *)start;
|
|
d = (unsigned char *)buf;
|
|
while (s < (unsigned char *)end) {
|
|
if (*s < 0x80) {
|
|
/* US-ASCII character: copy unless it's
|
|
* an apostrophe.
|
|
*/
|
|
if (*s != '\'')
|
|
*d++ = tolower (*s);
|
|
s++;
|
|
} else {
|
|
char *next = g_utf8_next_char (s);
|
|
uc = g_utf8_get_char (s);
|
|
if (uc >= 0xc0 && uc < 0xc0 + sizeof (utf8_trans)) {
|
|
signed char ch = utf8_trans[uc - 0xc0];
|
|
if (ch > 0)
|
|
*d++ = tolower (ch);
|
|
else {
|
|
*d++ = tolower (utf8_long_trans[-ch - 1][0]);
|
|
*d++ = tolower (utf8_long_trans[-ch - 1][1]);
|
|
}
|
|
s = next;
|
|
} else {
|
|
while (s < (unsigned char *)next)
|
|
*d++ = *s++;
|
|
}
|
|
}
|
|
}
|
|
*d = '\0';
|
|
}
|
|
|
|
enum { IBEX_ALPHA, IBEX_NONALPHA, IBEX_INVALID, IBEX_INCOMPLETE };
|
|
|
|
static int
|
|
utf8_category (char *p, char **np, char *end)
|
|
{
|
|
if (isascii ((unsigned char)*p)) {
|
|
*np = p + 1;
|
|
if (isalpha ((unsigned char)*p) || *p == '\'')
|
|
return IBEX_ALPHA;
|
|
return IBEX_NONALPHA;
|
|
} else {
|
|
gunichar uc;
|
|
|
|
*np = g_utf8_find_next_char (p, end);
|
|
if (!*np)
|
|
return IBEX_INCOMPLETE;
|
|
uc = g_utf8_get_char (p);
|
|
if (uc == (gunichar) -1)
|
|
return IBEX_INVALID;
|
|
else if (g_unichar_isalpha (uc))
|
|
return IBEX_ALPHA;
|
|
else
|
|
return IBEX_NONALPHA;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ibex_index_buffer: the lowest-level ibex indexing interface
|
|
* @ib: an ibex
|
|
* @name: the name of the file being indexed
|
|
* @buffer: a buffer containing data from the file
|
|
* @len: the length of @buffer
|
|
* @unread: an output argument containing the number of unread bytes
|
|
*
|
|
* This routine indexes up to @len bytes from @buffer into @ib.
|
|
* If @unread is NULL, the indexer assumes that the buffer ends on a
|
|
* word boundary, and will index all the way to the end of the
|
|
* buffer. If @unread is not NULL, and the buffer ends with an
|
|
* alphabetic character, the indexer will assume that the buffer has
|
|
* been cut off in the middle of a word, and return the number of
|
|
* un-indexed bytes at the end of the buffer in *@unread. The caller
|
|
* should then read in more data through whatever means it has
|
|
* and pass in the unread bytes from the original buffer, followed
|
|
* by the new data, on its next call.
|
|
*
|
|
* Return value: 0 on success, -1 on failure.
|
|
**/
|
|
int
|
|
ibex_index_buffer (ibex *ib, char *name, char *buffer, size_t len, size_t *unread)
|
|
{
|
|
char *p, *q, *nq, *end;
|
|
char *word;
|
|
int wordsiz, cat = 0;
|
|
GHashTable *words = g_hash_table_new(g_str_hash, g_str_equal);
|
|
GPtrArray *wordlist = g_ptr_array_new();
|
|
int i, ret=-1;
|
|
|
|
if (unread)
|
|
*unread = 0;
|
|
|
|
end = buffer + len;
|
|
wordsiz = 20;
|
|
word = g_malloc (wordsiz);
|
|
|
|
p = buffer;
|
|
while (p < end) {
|
|
while (p < end) {
|
|
cat = utf8_category (p, &q, end);
|
|
if (cat != IBEX_NONALPHA)
|
|
break;
|
|
p = q;
|
|
}
|
|
if (p == end) {
|
|
goto done;
|
|
} else if (cat == IBEX_INVALID) {
|
|
goto error;
|
|
} else if (cat == IBEX_INCOMPLETE)
|
|
q = end;
|
|
|
|
while (q < end) {
|
|
cat = utf8_category (q, &nq, end);
|
|
if (cat != IBEX_ALPHA)
|
|
break;
|
|
q = nq;
|
|
}
|
|
if (cat == IBEX_INVALID ||
|
|
(cat == IBEX_INCOMPLETE && !unread)) {
|
|
goto error;
|
|
} else if (cat == IBEX_INCOMPLETE || (q == end && unread)) {
|
|
*unread = end - p;
|
|
goto done;
|
|
}
|
|
|
|
if (wordsiz < q - p + 1) {
|
|
wordsiz = q - p + 1;
|
|
word = g_realloc (word, wordsiz);
|
|
}
|
|
ibex_normalise_word (p, q, word);
|
|
if (word[0]) {
|
|
if (g_hash_table_lookup(words, word) == 0) {
|
|
char *newword = g_strdup(word);
|
|
g_ptr_array_add(wordlist, newword);
|
|
g_hash_table_insert(words, newword, name);
|
|
}
|
|
}
|
|
p = q;
|
|
}
|
|
done:
|
|
/* this weird word=NULL shit is to get rid of compiler warnings about clobbering with longjmp */
|
|
g_free(word);
|
|
word = NULL;
|
|
|
|
IBEX_LOCK(ib);
|
|
|
|
ibex_use(ib);
|
|
|
|
if (ibex_block_cache_setjmp(ib->blocks) != 0) {
|
|
printf("Error in indexing\n");
|
|
ret = -1;
|
|
ibex_reset(ib);
|
|
word = NULL; /* here too */
|
|
} else {
|
|
word = NULL; /* ... and here */
|
|
d(printf("name %s count %d size %d\n", name, wordlist->len, len));
|
|
if (!ib->predone) {
|
|
ib->words->klass->index_pre(ib->words);
|
|
ib->predone = TRUE;
|
|
}
|
|
ib->words->klass->add_list(ib->words, name, wordlist);
|
|
ret = 0;
|
|
}
|
|
|
|
ibex_unuse(ib);
|
|
|
|
IBEX_UNLOCK(ib);
|
|
|
|
error:
|
|
for (i=0;i<wordlist->len;i++)
|
|
g_free(wordlist->pdata[i]);
|
|
g_ptr_array_free(wordlist, TRUE);
|
|
g_hash_table_destroy(words);
|
|
g_free(word);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* ibex_open:
|
|
* @file:
|
|
* @flags:
|
|
* @mode:
|
|
*
|
|
* Open a new ibex file. file, flags, and mode as for open(2)
|
|
*
|
|
* Return value: A new ibex, or NULL on failure.
|
|
**/
|
|
ibex *ibex_open (char *file, int flags, int mode)
|
|
{
|
|
ibex *ib;
|
|
|
|
ib = g_malloc0(sizeof(*ib));
|
|
ib->blocks = NULL;
|
|
ib->usecount = 0;
|
|
#if 0
|
|
ib->blocks = ibex_block_cache_open(file, flags, mode);
|
|
if (ib->blocks == 0) {
|
|
g_warning("create: Error occured?: %s\n", strerror(errno));
|
|
g_free(ib);
|
|
return NULL;
|
|
}
|
|
/* FIXME: the blockcache or the wordindex needs to manage the other one */
|
|
ib->words = ib->blocks->words;
|
|
#endif
|
|
ib->name = g_strdup(file);
|
|
ib->flags = flags;
|
|
ib->mode = mode;
|
|
|
|
#ifdef ENABLE_THREADS
|
|
pthread_mutex_init(&ib->lock, NULL);
|
|
#endif
|
|
|
|
IBEX_LIST_LOCK(ib);
|
|
ibex_list_addtail(&ibex_list, (struct _listnode *)ib);
|
|
IBEX_LIST_UNLOCK(ib);
|
|
|
|
return ib;
|
|
}
|
|
|
|
/**
|
|
* ibex_save:
|
|
* @ib:
|
|
*
|
|
* Save (sync) an ibex file to disk.
|
|
*
|
|
* Return value: -1 on failure.
|
|
**/
|
|
int ibex_save (ibex *ib)
|
|
{
|
|
int ret;
|
|
|
|
d(printf("syncing database\n"));
|
|
|
|
IBEX_LOCK(ib);
|
|
|
|
ibex_use(ib);
|
|
|
|
if (ibex_block_cache_setjmp(ib->blocks) != 0) {
|
|
ibex_reset(ib);
|
|
printf("Error saving\n");
|
|
ret = -1;
|
|
} else {
|
|
if (ib->predone) {
|
|
ib->words->klass->index_post(ib->words);
|
|
ib->predone = FALSE;
|
|
}
|
|
ib->words->klass->sync(ib->words);
|
|
/* FIXME: some return */
|
|
ibex_block_cache_sync(ib->blocks);
|
|
ret = 0;
|
|
}
|
|
|
|
ibex_unuse(ib);
|
|
|
|
IBEX_UNLOCK(ib);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
close_backend(ibex *ib)
|
|
{
|
|
int ret;
|
|
|
|
if (ib->blocks == 0)
|
|
return 0;
|
|
|
|
if (ibex_block_cache_setjmp(ib->blocks) != 0) {
|
|
printf("Error closing!\n");
|
|
ret = -1;
|
|
} else {
|
|
if (ib->predone) {
|
|
ib->words->klass->index_post(ib->words);
|
|
ib->predone = FALSE;
|
|
}
|
|
|
|
ib->words->klass->close(ib->words);
|
|
/* FIXME: return */
|
|
ibex_block_cache_close(ib->blocks);
|
|
ib->blocks = NULL;
|
|
ret = 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* close/reopen the ibex file, assumes we have lock */
|
|
static void
|
|
ibex_reset(ibex *ib)
|
|
{
|
|
g_warning("resetting ibex file");
|
|
|
|
close_backend(ib);
|
|
|
|
ib->blocks = ibex_block_cache_open(ib->name, ib->flags, ib->mode);
|
|
if (ib->blocks == 0) {
|
|
g_warning("ibex_reset create: Error occured?: %s\n", strerror(errno));
|
|
} else {
|
|
/* FIXME: the blockcache or the wordindex needs to manage the other one */
|
|
ib->words = ib->blocks->words;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ibex_close:
|
|
* @ib:
|
|
*
|
|
* Close (and save) an ibex file, restoring all resources used.
|
|
*
|
|
* Return value: -1 on error. In either case, ibex is no longer
|
|
* defined afterwards.
|
|
**/
|
|
int ibex_close (ibex *ib)
|
|
{
|
|
int ret;
|
|
|
|
d(printf("closing database\n"));
|
|
|
|
g_assert(ib->usecount == 0);
|
|
|
|
IBEX_LIST_LOCK(ib);
|
|
ibex_list_remove((struct _listnode *)ib);
|
|
IBEX_LIST_UNLOCK(ib);
|
|
|
|
if (ib->blocks != NULL)
|
|
ret = close_backend(ib);
|
|
else
|
|
ret = 0;
|
|
|
|
g_free(ib->name);
|
|
|
|
#ifdef ENABLE_THREADS
|
|
pthread_mutex_destroy(&ib->lock);
|
|
#endif
|
|
|
|
g_free(ib);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* rename/move the ibex file */
|
|
int ibex_move(ibex *ib, const char *newname)
|
|
{
|
|
int ret = 0, error = 0;
|
|
struct stat st;
|
|
|
|
IBEX_LOCK(ib);
|
|
|
|
if (ib->blocks)
|
|
close_backend(ib);
|
|
|
|
if (stat(ib->name, &st) == -1 && errno == ENOENT) {
|
|
error = 0;
|
|
} else if (rename(ib->name, newname) == -1) {
|
|
g_warning("could not rename ibex file '%s' to '%s': '%s'", ib->name, newname, strerror(errno));
|
|
ret = -1;
|
|
error = errno;
|
|
}
|
|
|
|
g_free(ib->name);
|
|
ib->name = g_strdup(newname);
|
|
|
|
IBEX_UNLOCK(ib);
|
|
|
|
if (ret == -1)
|
|
errno = error;
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* ibex_unindex:
|
|
* @ib:
|
|
* @name:
|
|
*
|
|
* Remove a name from the index.
|
|
**/
|
|
void ibex_unindex (ibex *ib, char *name)
|
|
{
|
|
d(printf("trying to unindex '%s'\n", name));
|
|
|
|
IBEX_LOCK(ib);
|
|
|
|
ibex_use(ib);
|
|
|
|
if (ibex_block_cache_setjmp(ib->blocks) != 0) {
|
|
printf("Error unindexing!\n");
|
|
ibex_reset(ib);
|
|
} else {
|
|
ib->words->klass->unindex_name(ib->words, name);
|
|
}
|
|
|
|
ibex_unuse(ib);
|
|
|
|
IBEX_UNLOCK(ib);
|
|
}
|
|
|
|
/**
|
|
* ibex_find:
|
|
* @ib:
|
|
* @word:
|
|
*
|
|
* Find all names containing @word.
|
|
*
|
|
* Return value:
|
|
**/
|
|
GPtrArray *ibex_find (ibex *ib, char *word)
|
|
{
|
|
char *normal;
|
|
int len;
|
|
GPtrArray *ret;
|
|
|
|
len = strlen(word);
|
|
normal = alloca(len+1);
|
|
ibex_normalise_word(word, word+len, normal);
|
|
|
|
IBEX_LOCK(ib);
|
|
ibex_use(ib);
|
|
|
|
if (ibex_block_cache_setjmp(ib->blocks) != 0) {
|
|
ibex_reset(ib);
|
|
ret = NULL;
|
|
} else {
|
|
ret = ib->words->klass->find(ib->words, normal);
|
|
}
|
|
|
|
ibex_unuse(ib);
|
|
IBEX_UNLOCK(ib);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* ibex_find_name:
|
|
* @ib:
|
|
* @name:
|
|
* @word:
|
|
*
|
|
* Return #TRUE if the specific @word is contained in @name.
|
|
*
|
|
* Return value:
|
|
**/
|
|
gboolean ibex_find_name (ibex *ib, char *name, char *word)
|
|
{
|
|
char *normal;
|
|
int len;
|
|
gboolean ret;
|
|
|
|
len = strlen(word);
|
|
normal = alloca(len+1);
|
|
ibex_normalise_word(word, word+len, normal);
|
|
|
|
IBEX_LOCK(ib);
|
|
ibex_use(ib);
|
|
|
|
if (ibex_block_cache_setjmp(ib->blocks) != 0) {
|
|
ibex_reset(ib);
|
|
ret = FALSE;
|
|
} else {
|
|
ret = ib->words->klass->find_name(ib->words, name, normal);
|
|
}
|
|
|
|
ibex_unuse(ib);
|
|
IBEX_UNLOCK(ib);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* ibex_contains_name:
|
|
* @ib:
|
|
* @name:
|
|
*
|
|
* Returns true if the name @name is somewhere in the database.
|
|
*
|
|
* Return value:
|
|
**/
|
|
gboolean ibex_contains_name(ibex *ib, char *name)
|
|
{
|
|
gboolean ret;
|
|
|
|
IBEX_LOCK(ib);
|
|
ibex_use(ib);
|
|
|
|
if (ibex_block_cache_setjmp(ib->blocks) != 0) {
|
|
ibex_reset(ib);
|
|
ret = FALSE;
|
|
} else {
|
|
ret = ib->words->klass->contains_name(ib->words, name);
|
|
}
|
|
|
|
ibex_unuse(ib);
|
|
IBEX_UNLOCK(ib);
|
|
|
|
return ret;
|
|
}
|
|
|