aboutsummaryrefslogblamecommitdiffstats
path: root/libibex/ibex_block.c
blob: 054a8791cfccebc4dab8986c1247324240ca3a05 (plain) (tree)
1
2
3
4
5
6
7
8
9
 



                    
                 
 
                  
                                 
                  
                   
                  
                   
 



                      



                    

                          
            
             
 
                                 


                                                                                                                

                                                     



                                                                  
                                                                      











                                                                    
                      


                                        





















                                                                                                  












                                                                          

                                                                        







                                                                                                               
                         
                                        
                 

                              






                                
                       

 
                                   













                                                                             


                                  

                                                             











                                                                          
                                                      
 
                             
                    











                                                               

                                                          












                                                                                     
                 
         
                  



                                                                  
          
                                             
 
                                          
                            
                                                              


                                          
                            
 

                                                     
                                               



                                                


                                             
         

 




















                                                                    
   
                                                                                  
 

                               
                             


                                                                      
 


                            












                                                         
                                  
                                                 
                                   










                                                          
                                   

                                                                            
                                  
                 
 



                                                         







                                                                          
                      
         
     



                                                                                                        

                      

                     













                                                                                  
         
 
                       
 

                        




                                           
                     



                   









                                                              




                                                 


                          





                                                                           


                                                                                  



                                  
                     
                                            
      




                                                              


                  







                                    

                        

                
                                        


                      

                     












                                                                
         
 
                       
 

                        







                       


                            











                                                                
                                  
















                                                                                      


                                                                                          
         
 
 








                                                                

                         
                
 
                                        
 









                                                 

                         
 
                     
                                         
      
 
                   
 


                   


                                            
                               
                       





                                  


                                                           
                                                                                                               


                              


                                     
 

                        


                              



                   






                                


                                                    
 
                      
 

                     





                                                                
 
                       

                        

 








                                   



                                           
                       



                                                    
 
                      
                     
 





                                                                
 
                       
                        
 
                   

 









                                                            



                                                          
                     



                                                    
 
                      
                     
 





                                                                           
 
                       
                        
 
                   

 








                                                               

                                                 


                      
                     
 





                                                                       
 
                       
                        
 
                   
 
 

#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;
}