/*....................................................................... * Private function used to allocate a hash-table node. * The caller is responsible for checking that the specified symbol * is unique and for installing the returned entry in the table. * * Input: * hash HashTable * The table to allocate the node for. * name const char * The name of the new entry. * code int A user-supplied context code. * fn void (*)(void) A user-supplied function pointer. * data void * A user-supplied data pointer. * del_fn SYM_DEL_FN(*) An optional 'data' destructor function. * Output: * return HashNode * The new node, or NULL on error. */ static HashNode *_new_HashNode(HashTable *hash, const char *name, int code, void (*fn)(void), void *data, SYM_DEL_FN(*del_fn)) { HashNode *node; /* The new node */ size_t len; /* * Allocate the new node from the free list. */ node = (HashNode *) _new_FreeListNode(hash->mem->node_memory); if(!node) return NULL; /* * Before attempting any operation that might fail, initialize the * contents of 'node' at least up to the point at which it can be * safely passed to _del_HashNode(). */ node->symbol.name = NULL; node->symbol.code = code; node->symbol.fn = fn; node->symbol.data = data; node->symbol.del_fn = del_fn; node->next = NULL; /* * Allocate a copy of 'name'. */ len = strlen(name) + 1; node->symbol.name = _new_StringMemString(hash->mem->string_memory, len); if(!node->symbol.name) return _del_HashNode(hash, node); /* * If character-case is insignificant in the current table, convert the * name to lower case while copying it. */ if(hash->case_sensitive) { strlcpy(node->symbol.name, name, len); } else { const char *src = name; char *dst = node->symbol.name; for( ; *src; src++,dst++) *dst = tolower(*src); *dst = '\0'; }; return node; }
/*....................................................................... * Allocate an array of 'length' chars. * * Input: * sm StringMem * The string free-list to allocate from. * length size_t The length of the new string (including '\0'). * Output: * return char * The new string or NULL on error. */ char *_new_StringMemString(StringMem *sm, size_t length) { char *string; /* The string to be returned */ int was_malloc; /* True if malloc was used to allocate the string */ /* * Check arguments. */ if(!sm) return NULL; if(length < 1) length = 1; /* * Allocate the new node from the free list if possible. */ if(length < SM_STRLEN) { string = (char *)_new_FreeListNode(sm->fl); if(!string) return NULL; was_malloc = 0; } else { string = (char *) malloc(length+1); /* Leave room for the flag byte */ if(!string) return NULL; /* * Count malloc allocations. */ was_malloc = 1; sm->nmalloc++; }; /* * Use the first byte of the string to record whether the string was * allocated with malloc or from the free-list. Then return the rest * of the string for use by the user. */ string[0] = (char) was_malloc; return string + 1; }
/*....................................................................... * Create a new hash table. * * Input: * mem HashMemory * An optional free-list for use in allocating * HashTable containers and nodes. See explanation * in hash.h. If you are going to allocate more * than one hash table, then it will be more * efficient to allocate a single free-list for * all of them than to force each hash table * to allocate its own private free-list. * size int The size of the hash table. Best performance * will be acheived if this is a prime number. * hcase HashCase Specify how symbol case is considered when * looking up symbols, from: * IGNORE_CASE - Upper and lower case versions * of a letter are treated as * being identical. * HONOUR_CASE - Upper and lower case versions * of a letter are treated as * being distinct. * characters in a lookup name is significant. * app_data void * Optional application data to be registered * to the table. This is presented to user * provided SYM_DEL_FN() symbol destructors along * with the symbol data. * del_fn() HASH_DEL_FN(*) If you want app_data to be free'd when the * hash-table is destroyed, register a suitable * destructor function here. * Output: * return HashTable * The new hash table, or NULL on error. */ HashTable *_new_HashTable(HashMemory *mem, int size, HashCase hcase, void *app_data, HASH_DEL_FN(*del_fn)) { HashTable *hash; /* The table to be returned */ int allocate_mem = !mem; /* True if mem should be internally allocated */ int i; /* * Check arguments. */ if(size <= 0) { errno = EINVAL; return NULL; }; /* * Allocate an internal free-list? */ if(allocate_mem) { mem = _new_HashMemory(1, 100); if(!mem) return NULL; }; /* * Allocate the container. */ hash = (HashTable *) _new_FreeListNode(mem->hash_memory); if(!hash) { errno = ENOMEM; if(allocate_mem) mem = _del_HashMemory(mem, 1); return NULL; }; /* * Before attempting any operation that might fail, initialize * the container at least up to the point at which it can safely * be passed to _del_HashTable(). */ hash->mem = mem; hash->internal_mem = allocate_mem; hash->case_sensitive = hcase==HONOUR_CASE; hash->size = size; hash->bucket = NULL; hash->keycmp = hash->case_sensitive ? _ht_strcmp : _ht_lower_strcmp; hash->app_data = app_data; hash->del_fn = del_fn; /* * Allocate the array of 'size' hash buckets. */ hash->bucket = (HashBucket *) malloc(sizeof(HashBucket) * size); if(!hash->bucket) { errno = ENOMEM; return _del_HashTable(hash); }; /* * Initialize the bucket array. */ for(i=0; i<size; i++) { HashBucket *b = hash->bucket + i; b->head = NULL; b->count = 0; }; /* * The table is ready for use - albeit currently empty. */ return hash; }
/*....................................................................... * Get a new directory reader object from the cache. * * Input: * ef ExpandFile * The pathname expansion resource object. * pathname const char * The pathname of the directory. * Output: * return DirNode * The cache entry of the new directory reader, * or NULL on error. On error, ef->errmsg will * contain a description of the error. */ static DirNode *ef_open_dir(ExpandFile *ef, const char *pathname) { char *errmsg = NULL; /* An error message from a called function */ DirNode *node; /* The cache node used */ /* * Get the directory reader cache. */ DirCache *cache = &ef->cache; /* * Extend the cache if there are no free cache nodes. */ if(!cache->next) { node = (DirNode *) _new_FreeListNode(cache->mem); if(!node) { snprintf(ef->errmsg, sizeof(ef->errmsg), "Insufficient memory to open a new directory"); return NULL; }; /* * Initialize the cache node. */ node->next = NULL; node->prev = NULL; node->dr = NULL; /* * Allocate a directory reader object. */ node->dr = _new_DirReader(); if(!node->dr) { snprintf(ef->errmsg, sizeof(ef->errmsg), "Insufficient memory to open a new directory"); node = (DirNode *) _del_FreeListNode(cache->mem, node); return NULL; }; /* * Append the node to the cache list. */ node->prev = cache->tail; if(cache->tail) cache->tail->next = node; else cache->head = node; cache->next = cache->tail = node; }; /* * Get the first unused node, but don't remove it from the list yet. */ node = cache->next; /* * Attempt to open the specified directory. */ if(_dr_open_dir(node->dr, pathname, &errmsg)) { strncpy(ef->errmsg, errmsg, ERRLEN); ef->errmsg[ERRLEN] = '\0'; return NULL; }; /* * Now that we have successfully opened the specified directory, * remove the cache node from the list, and relink the list around it. */ cache->next = node->next; if(node->prev) node->prev->next = node->next; else cache->head = node->next; if(node->next) node->next->prev = node->prev; else cache->tail = node->prev; node->next = node->prev = NULL; /* * Return the successfully initialized cache node to the caller. */ return node; }
/*....................................................................... * Append an array of n characters to a character queue. * * Input: * cq GlCharQueue * The queue to append to. * chars const char * The array of n characters to be appended. * n int The number of characters in chars[]. * write_fn GL_WRITE_FN * The function to call to output characters, * or 0 to simply discard the contents of the * queue. This will be called whenever the * buffer becomes full. If it fails to release * any space, the buffer will be extended. * data void * Anonymous data to pass to write_fn(). * Output: * return int The number of characters successfully * appended. This will only be < n on error. */ int _glq_append_chars(GlCharQueue *cq, const char *chars, int n, GlWriteFn *write_fn, void *data) { int ndone = 0; /* The number of characters appended so far */ /* * Check the arguments. */ if(!cq || !chars) { errno = EINVAL; return 0; }; /* * The appended characters may have to be split between multiple * buffers, so loop for each buffer. */ while(ndone < n) { int ntodo; /* The number of characters remaining to be appended */ int nleft; /* The amount of space remaining in cq->buffers.tail */ int nnew; /* The number of characters to append to cq->buffers.tail */ /* * Compute the offset at which the next character should be written * into the tail buffer segment. */ int boff = cq->ntotal % GL_CQ_SIZE; /* * Since we don't allocate a new buffer until we have at least one * character to write into it, if boff is 0 at this point, it means * that we hit the end of the tail buffer segment on the last append, * so we need to allocate a new one. * * If allocating this new node will require a call to malloc(), as * opposed to using a currently unused node in the freelist, first try * flushing the current contents of the buffer to the terminal. When * write_fn() uses blocking I/O, this stops the buffer size ever getting * bigger than a single buffer node. When it is non-blocking, it helps * to keep the amount of memory, but it isn't gauranteed to do so. */ if(boff == 0 && _idle_FreeListNodes(cq->bufmem) == 0) { switch(_glq_flush_queue(cq, write_fn, data)) { case GLQ_FLUSH_DONE: break; case GLQ_FLUSH_AGAIN: errno = 0; /* Don't confuse the caller */ break; default: return ndone; /* Error */ }; boff = cq->ntotal % GL_CQ_SIZE; }; /* * Since we don't allocate a new buffer until we have at least one * character to write into it, if boff is 0 at this point, it means * that we hit the end of the tail buffer segment on the last append, * so we need to allocate a new one. */ if(boff == 0) { /* * Allocate the new node. */ CqCharBuff *node = (CqCharBuff *) _new_FreeListNode(cq->bufmem); if(!node) { _err_record_msg(cq->err, "Insufficient memory to buffer output.", END_ERR_MSG); return ndone; }; /* * Initialize the node. */ node->next = NULL; /* * Append the new node to the tail of the list. */ if(cq->buffers.tail) cq->buffers.tail->next = node; else cq->buffers.head = node; cq->buffers.tail = node; }; /* * How much room is there for new characters in the current tail node? */ nleft = GL_CQ_SIZE - boff; /* * How many characters remain to be appended? */ ntodo = n - ndone; /* * How many characters should we append to the current tail node? */ nnew = nleft < ntodo ? nleft : ntodo; /* * Append the latest prefix of nnew characters. */ memcpy(cq->buffers.tail->bytes + boff, chars + ndone, nnew); cq->ntotal += nnew; ndone += nnew; }; /* * Return the count of the number of characters successfully appended. */ return ndone; }