/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * utils.c: Various utility routines that should have been in glib. * * Authors: * Miguel de Icaza (miguel@gnu.org) * Jukka-Pekka Iivonen (iivonen@iki.fi) * Zbigniew Chyla (cyba@gnome.pl) * Morten Welinder (terra@gnome.org) */ #include #include "go-glib-extras.h" #include #include #include #include #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include static void cb_hash_collect_keys (gpointer key, gpointer value, GSList **accum) { *accum = g_slist_prepend (*accum, key); } /** * go_hash_keys : * @hash : #GHashTable * * Collects an unordered list of the keys in @hash. * * Returns a list which the caller needs to free. * The content has not additional references added **/ GSList * go_hash_keys (GHashTable *hash) { GSList *accum = NULL; g_hash_table_foreach (hash, (GHFunc )cb_hash_collect_keys, &accum); return accum; } static void cb_hash_collect_values (gpointer key, gpointer value, GSList **accum) { *accum = g_slist_prepend (*accum, value); } /** * go_hash_values : * @hash : #GHashTable * * Collects an unordered list of the values in @hash. * * Returns a list which the caller needs to free. * The content has not additional references added **/ GSList * go_hash_values (GHashTable *hash) { GSList *accum = NULL; g_hash_table_foreach (hash, (GHFunc )cb_hash_collect_values, &accum); return accum; } /***************************************************************************/ void go_ptr_array_insert (GPtrArray *array, gpointer value, int index) { if (index < (int)array->len) { int i = array->len - 1; gpointer last = g_ptr_array_index (array, i); g_ptr_array_add (array, last); while (i-- > index) { gpointer tmp = g_ptr_array_index (array, i); g_ptr_array_index (array, i + 1) = tmp; } g_ptr_array_index (array, index) = value; } else g_ptr_array_add (array, value); } /** * go_slist_create: * @item1: First item. * * Creates a GList from NULL-terminated list of arguments. * * Return value: created list. **/ GSList * go_slist_create (gpointer item1, ...) { va_list args; GSList *list = NULL; gpointer item; va_start (args, item1); for (item = item1; item != NULL; item = va_arg (args, gpointer)) { list = g_slist_prepend (list, item); } va_end (args); return g_slist_reverse (list); } /** * go_slist_map: * @list : list of some items * @map_func : mapping function * **/ GSList * go_slist_map (GSList const *list, GOMapFunc map_func) { GSList *list_copy = NULL; GO_SLIST_FOREACH (list, void, value, GO_SLIST_PREPEND (list_copy, map_func (value)) ); return g_slist_reverse (list_copy); } /** * go_slist_free_custom: * @list: list of some items * @free_func: function freeing list item * * Clears a list, calling @free_func for each list item. **/ void go_slist_free_custom (GSList *list, GFreeFunc free_func) { GSList *l; for (l = list; l != NULL; l = l->next) { free_func (l->data); } g_slist_free (list); } gint go_list_index_custom (GList *list, gpointer data, GCompareFunc cmp_func) { GList *l; gint i; for (l = list, i = 0; l != NULL; l = l->next, i++) { if (cmp_func (l->data, data) == 0) { return i; } } return -1; } /** * go_list_free_custom: * @list: list of some items * @free_func: function freeing list item * * Clears a list, calling @free_func for each list item. * */ void go_list_free_custom (GList *list, GFreeFunc free_func) { GList *l; for (l = list; l != NULL; l = l->next) { free_func (l->data); } g_list_free (list); } /** * go_strsplit_to_slist: * @str: String to split * @delimiter: Token delimiter * * Splits up string into tokens at delim and returns a string list. * * Returns: string list which you should free after use using function * e_free_string_list(). **/ GSList * go_strsplit_to_slist (gchar const *string, gchar delimiter) { gchar **token_v; GSList *string_list = NULL; char buf[2] = { '\0', '\0' }; gint i; buf[0] = delimiter; token_v = g_strsplit (string, buf, 0); if (token_v != NULL) { for (i = 0; token_v[i] != NULL; i++) { string_list = g_slist_prepend (string_list, token_v[i]); } string_list = g_slist_reverse (string_list); g_free (token_v); } return string_list; } gint go_utf8_collate_casefold (const char *a, const char *b) { char *a2 = g_utf8_casefold (a, -1); char *b2 = g_utf8_casefold (b, -1); int res = g_utf8_collate (a2, b2); g_free (a2); g_free (b2); return res; } gint go_ascii_strcase_equal (gconstpointer v1, gconstpointer v2) { return g_ascii_strcasecmp ((char const *) v1, (char const *)v2) == 0; } /* a char* hash function from ASU */ guint go_ascii_strcase_hash (gconstpointer v) { unsigned const char *s = (unsigned const char *)v; unsigned const char *p; guint h = 0, g; for(p = s; *p != '\0'; p += 1) { h = ( h << 4 ) + g_ascii_tolower (*p); if ( ( g = h & 0xf0000000 ) ) { h = h ^ (g >> 24); h = h ^ g; } } return h /* % M */; } /* ------------------------------------------------------------------------- */ /* * Escapes all backslashes and quotes in a string. It is based on glib's * g_strescape. * * Also adds quotes around the result. */ void go_strescape (GString *target, char const *string) { g_string_append_c (target, '"'); /* This loop should be UTF-8 safe. */ for (; *string; string++) { switch (*string) { case '"': case '\\': g_string_append_c (target, '\\'); default: g_string_append_c (target, *string); } } g_string_append_c (target, '"'); } /* * The reverse operation of go_strescape. Returns a pointer to the * first char after the string on success or NULL on failure. * * First character of the string should be an ASCII character used * for quoting. */ const char * go_strunescape (GString *target, const char *string) { char quote = *string++; size_t oldlen = target->len; /* This should be UTF-8 safe as long as quote is ASCII. */ while (*string != quote) { if (*string == 0) goto error; else if (*string == '\\') { string++; if (*string == 0) goto error; } g_string_append_c (target, *string); string++; } return ++string; error: g_string_truncate (target, oldlen); return NULL; } void go_string_append_gstring (GString *target, const GString *source) { g_string_append_len (target, source->str, source->len); } /* ------------------------------------------------------------------------- */ /** * go_utf8_strcapital: * @p: pointer to UTF-8 string * @len: length in bytes, or -1. * * Similar to g_utf8_strup and g_utf8_strup, except that this function * creates a string "Very Much Like: This, One". * * Return value: newly allocated string. **/ char * go_utf8_strcapital (const char *p, gssize len) { const char *pend = (len < 0 ? NULL : p + len); GString *res = g_string_sized_new (len < 0 ? 1 : len + 1); gboolean up = TRUE; /* * This does a simple character-by-character mapping and probably * is not linguistically correct. */ for (; (len < 0 || p < pend) && *p; p = g_utf8_next_char (p)) { gunichar c = g_utf8_get_char (p); if (g_unichar_isalpha (c)) { if (up ? g_unichar_isupper (c) : g_unichar_islower (c)) /* Correct case -- keep the char. */ g_string_append_unichar (res, c); else { char *tmp = up ? g_utf8_strup (p, 1) : g_utf8_strdown (p, 1); g_string_append (res, tmp); g_free (tmp); } up = FALSE; } else { g_string_append_unichar (res, c); up = TRUE; } } return g_string_free (res, FALSE); } /* ------------------------------------------------------------------------- */ #undef DEBUG_CHUNK_ALLOCATOR typedef struct _go_mem_chunk_freeblock go_mem_chunk_freeblock; typedef struct _go_mem_chunk_block go_mem_chunk_block; struct _go_mem_chunk_freeblock { go_mem_chunk_freeblock *next; }; struct _go_mem_chunk_block { gpointer data; int freecount, nonalloccount; go_mem_chunk_freeblock *freelist; #ifdef DEBUG_CHUNK_ALLOCATOR int id; #endif }; struct _GOMemChunk { char *name; size_t atom_size, user_atom_size, chunk_size, alignment; int atoms_per_block; /* List of all blocks. */ GSList *blocklist; /* List of blocks that are not full. */ GList *freeblocks; #ifdef DEBUG_CHUNK_ALLOCATOR int blockid; #endif }; GOMemChunk * go_mem_chunk_new (char const *name, size_t user_atom_size, size_t chunk_size) { int atoms_per_block; GOMemChunk *res; size_t user_alignment, alignment, atom_size; size_t maxalign = 1 + ((sizeof (void *) - 1) | (sizeof (long) - 1) | (sizeof (double) - 1)); /* * The alignment that the caller can expect is 2^(lowest_bit_in_size). * The fancy bit math computes this. Think it over. * * We don't go lower than pointer-size, so this always comes out as * 4 or 8. Sometimes, when user_atom_size is a multiple of 8, this * alignment is bigger than really needed, but we don't know if the * structure has elements with 8-byte alignment. In those cases we * waste memory. */ user_alignment = ((user_atom_size ^ (user_atom_size - 1)) + 1) / 2; alignment = MIN (MAX (user_alignment, sizeof (go_mem_chunk_block *)), maxalign); atom_size = alignment + MAX (user_atom_size, sizeof (go_mem_chunk_freeblock)); atoms_per_block = MAX (1, chunk_size / atom_size); chunk_size = atoms_per_block * atom_size; #ifdef DEBUG_CHUNK_ALLOCATOR g_print ("Created %s with alignment=%d, atom_size=%d (%d), chunk_size=%d.\n", name, alignment, atom_size, user_atom_size, chunk_size); #endif res = g_new (GOMemChunk, 1); res->alignment = alignment; res->name = g_strdup (name); res->user_atom_size = user_atom_size; res->atom_size = atom_size; res->chunk_size = chunk_size; res->atoms_per_block = atoms_per_block; res->blocklist = NULL; res->freeblocks = NULL; #ifdef DEBUG_CHUNK_ALLOCATOR res->blockid = 0; #endif return res; } void go_mem_chunk_destroy (GOMemChunk *chunk, gboolean expect_leaks) { GSList *l; g_return_if_fail (chunk != NULL); #ifdef DEBUG_CHUNK_ALLOCATOR g_print ("Destroying %s.\n", chunk->name); #endif /* * Since this routine frees all memory allocated for the pool, * it is sometimes convenient not to free at all. For such * cases, don't report leaks. */ if (!expect_leaks) { GSList *l; int leaked = 0; for (l = chunk->blocklist; l; l = l->next) { go_mem_chunk_block *block = l->data; leaked += chunk->atoms_per_block - (block->freecount + block->nonalloccount); } if (leaked) { g_warning ("Leaked %d nodes from %s.", leaked, chunk->name); } } for (l = chunk->blocklist; l; l = l->next) { go_mem_chunk_block *block = l->data; g_free (block->data); g_free (block); } g_slist_free (chunk->blocklist); g_list_free (chunk->freeblocks); g_free (chunk->name); g_free (chunk); } gpointer go_mem_chunk_alloc (GOMemChunk *chunk) { go_mem_chunk_block *block; char *res; /* First try the freelist. */ if (chunk->freeblocks) { go_mem_chunk_freeblock *res; block = chunk->freeblocks->data; res = block->freelist; if (res) { block->freelist = res->next; block->freecount--; if (block->freecount == 0 && block->nonalloccount == 0) { /* Block turned full -- remove it from freeblocks. */ chunk->freeblocks = g_list_delete_link (chunk->freeblocks, chunk->freeblocks); } return res; } /* * If we get here, the block has free space that was never * allocated. */ } else { block = g_new (go_mem_chunk_block, 1); #ifdef DEBUG_CHUNK_ALLOCATOR block->id = chunk->blockid++; g_print ("Allocating new chunk %d for %s.\n", block->id, chunk->name); #endif block->nonalloccount = chunk->atoms_per_block; block->freecount = 0; block->data = g_malloc (chunk->chunk_size); block->freelist = NULL; chunk->blocklist = g_slist_prepend (chunk->blocklist, block); chunk->freeblocks = g_list_prepend (chunk->freeblocks, block); } res = (char *)block->data + (chunk->atoms_per_block - block->nonalloccount--) * chunk->atom_size; *((go_mem_chunk_block **)res) = block; if (block->nonalloccount == 0 && block->freecount == 0) { /* Block turned full -- remove it from freeblocks. */ chunk->freeblocks = g_list_delete_link (chunk->freeblocks, chunk->freeblocks); } return res + chunk->alignment; } gpointer go_mem_chunk_alloc0 (GOMemChunk *chunk) { gpointer res = go_mem_chunk_alloc (chunk); memset (res, 0, chunk->user_atom_size); return res; } void go_mem_chunk_free (GOMemChunk *chunk, gpointer mem) { go_mem_chunk_freeblock *fb = mem; go_mem_chunk_block *block = *((go_mem_chunk_block **)((char *)mem - chunk->alignment)); #if 0 /* * This is useful when the exact location of a leak needs to be * pin-pointed. */ memset (mem, 0, chunk->user_atom_size); #endif fb->next = block->freelist; block->freelist = fb; block->freecount++; if (block->freecount == 1 && block->nonalloccount == 0) { /* Block turned non-full. */ chunk->freeblocks = g_list_prepend (chunk->freeblocks, block); } else if (block->freecount == chunk->atoms_per_block) { /* Block turned all-free. */ #ifdef DEBUG_CHUNK_ALLOCATOR g_print ("Releasing chunk %d for %s.\n", block->id, chunk->name); #endif /* * FIXME -- this could be faster if we rolled our own lists. * Hopefully, however, (a) the number of blocks is small, * and (b) the freed block might be near the beginning ("top") * of the stacks. */ chunk->blocklist = g_slist_remove (chunk->blocklist, block); chunk->freeblocks = g_list_remove (chunk->freeblocks, block); g_free (block->data); g_free (block); } } /* * Loop over all non-freed memory in the chunk. It's safe to allocate or free * from the chunk in the callback. */ void go_mem_chunk_foreach_leak (GOMemChunk *chunk, GFunc cb, gpointer user) { GSList *l, *leaks = NULL; for (l = chunk->blocklist; l; l = l->next) { go_mem_chunk_block *block = l->data; if (chunk->atoms_per_block - (block->freecount + block->nonalloccount) > 0) { char *freed = g_new0 (char, chunk->atoms_per_block); go_mem_chunk_freeblock *fb = block->freelist; int i; while (fb) { char *atom = (char *)fb - chunk->alignment; int no = (atom - (char *)block->data) / chunk->atom_size; freed[no] = 1; fb = fb->next; } for (i = chunk->atoms_per_block - block->nonalloccount - 1; i >= 0; i--) { if (!freed[i]) { char *atom = (char *)block->data + i * chunk->atom_size; leaks = g_slist_prepend (leaks, atom + chunk->alignment); } } g_free (freed); } } g_slist_foreach (leaks, cb, user); g_slist_free (leaks); } int go_str_compare (void const *x, void const *y) { if (x == NULL || y == NULL) { if (x == y) return 0; else return x ? -1 : 1; } return strcmp (x, y); } const char * go_guess_encoding (const char *raw, size_t len, const char *user_guess, char **utf8_str) { int try; gboolean debug = FALSE; g_return_val_if_fail (raw != NULL, NULL); for (try = 1; 1; try++) { const char *guess; GError *error = NULL; char *utf8_data; switch (try) { case 1: guess = user_guess; break; case 2: g_get_charset (&guess); break; case 3: { xmlCharEncoding enc = xmlDetectCharEncoding ((const unsigned char*)raw, len); switch (enc) { case XML_CHAR_ENCODING_ERROR: case XML_CHAR_ENCODING_NONE: break; case XML_CHAR_ENCODING_UTF16LE: /* Default would give "UTF-16". */ guess = "UTF-16LE"; break; case XML_CHAR_ENCODING_UTF16BE: /* Default would give "UTF-16". */ guess = "UTF-16BE"; break; default: guess = xmlGetCharEncodingName (enc); } break; } case 4: guess = "ASCII"; break; case 5: guess = "ISO-8859-1"; break; case 6: guess = "UTF-8"; break; default: return NULL; } if (!guess) continue; if (debug) g_print ("Trying %s as encoding.\n", guess); utf8_data = g_convert (raw, len, "UTF-8", guess, NULL, NULL, &error); if (!error) { if (debug) g_print ("Guessed %s as encoding.\n", guess); if (utf8_str) *utf8_str = utf8_data; else g_free (utf8_data); return guess; } g_error_free (error); } } /** * go_get_real_name : * * Return a utf8 encoded string with the current user name. * Caller should _NOT_ free the result. **/ char const * go_get_real_name (void) { /* We will leak this. */ static char *go_real_name = NULL; if (go_real_name == NULL) { char const *name = getenv ("NAME"); if (name == NULL) name = g_get_real_name (); if (name == NULL) name = g_get_user_name (); if (name != NULL) (void) go_guess_encoding (name, strlen (name), NULL, &go_real_name); else go_real_name = (char *)"unknown"; } return go_real_name; } /** * go_destroy_password : * * Overwrite a string holding a password. This is a separate routine to * ensure that the compiler does not try to outsmart us. * * Note: this does not free the memory. **/ void go_destroy_password (char *passwd) { memset (passwd, 0, strlen (passwd)); } /** * go_object_toggle: toggle a boolean object property. * **/ void go_object_toggle (gpointer object, const gchar *property_name) { gboolean value = FALSE; GParamSpec *pspec; g_return_if_fail (G_IS_OBJECT (object)); g_return_if_fail (property_name != NULL); pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (object), property_name); if (!pspec || !G_IS_PARAM_SPEC_BOOLEAN (pspec) || ((pspec->flags & (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)) != G_PARAM_READWRITE)) { g_warning ("%s: object class `%s' has no boolean property named `%s' that can be both read and written.", G_STRFUNC, G_OBJECT_TYPE_NAME (object), property_name); return; } /* And now, the actual action. */ g_object_get (object, property_name, &value, NULL); g_object_set (object, property_name, !value, NULL); } /* * Collect all rw properties and their values. */ GSList * go_object_properties_collect (GObject *obj) { GSList *res = NULL; guint n; GParamSpec **pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (obj), &n); while (n--) { GParamSpec *pspec = pspecs[n]; if ((pspec->flags & (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)) == G_PARAM_READWRITE) { GValue *value = g_new0 (GValue, 1); g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (pspec)); g_object_get_property (obj, pspec->name, value); res = g_slist_prepend (res, value); res = g_slist_prepend (res, pspec); } } g_free (pspecs); return res; } void go_object_properties_apply (GObject *obj, GSList *props, gboolean changed_only) { GValue current = { 0, }; for (; props; props = props->next->next) { GParamSpec *pspec = props->data; const GValue *value = props->next->data; gboolean doit; if (changed_only) { g_value_init (¤t, G_PARAM_SPEC_VALUE_TYPE (pspec)); g_object_get_property (obj, pspec->name, ¤t); doit = g_param_values_cmp (pspec, ¤t, value); #if 0 g_print ("%2d: old: [%s] new: [%s]\n", g_param_values_cmp (pspec, ¤t, value), g_strdup_value_contents (value), g_strdup_value_contents (¤t)); #endif g_value_unset (¤t); } else doit = TRUE; if (doit) g_object_set_property (obj, pspec->name, value); } } void go_object_properties_free (GSList *props) { GSList *l; for (l = props; l; l = l->next->next) { GValue *value = l->next->data; g_value_unset (value); g_free (value); } g_slist_free (props); }