2000-04-26 04:03:38 -04:00
|
|
|
/*
|
|
|
|
misc.c : irssi
|
|
|
|
|
|
|
|
Copyright (C) 1999 Timo Sirainen
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
2007-05-08 14:41:10 -04:00
|
|
|
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.,
|
|
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
2000-04-26 04:03:38 -04:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "module.h"
|
|
|
|
#include "misc.h"
|
|
|
|
|
2000-10-26 14:12:20 -04:00
|
|
|
#ifdef HAVE_REGEX_H
|
|
|
|
# include <regex.h>
|
|
|
|
#endif
|
2000-04-26 04:03:38 -04:00
|
|
|
|
|
|
|
typedef struct {
|
2000-11-23 18:29:32 -05:00
|
|
|
int condition;
|
2000-04-26 04:03:38 -04:00
|
|
|
GInputFunction function;
|
2000-07-16 16:18:05 -04:00
|
|
|
void *data;
|
2000-04-26 04:03:38 -04:00
|
|
|
} IRSSI_INPUT_REC;
|
|
|
|
|
2000-07-16 16:18:05 -04:00
|
|
|
static int irssi_io_invoke(GIOChannel *source, GIOCondition condition,
|
|
|
|
void *data)
|
2000-04-26 04:03:38 -04:00
|
|
|
{
|
|
|
|
IRSSI_INPUT_REC *rec = data;
|
2000-11-23 18:29:32 -05:00
|
|
|
int icond = 0;
|
2000-04-26 04:03:38 -04:00
|
|
|
|
2000-08-14 20:22:08 -04:00
|
|
|
if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) {
|
|
|
|
/* error, we have to call the function.. */
|
|
|
|
if (rec->condition & G_IO_IN)
|
|
|
|
icond |= G_INPUT_READ;
|
|
|
|
else
|
|
|
|
icond |= G_INPUT_WRITE;
|
|
|
|
}
|
|
|
|
|
2000-04-26 04:03:38 -04:00
|
|
|
if (condition & (G_IO_IN | G_IO_PRI))
|
|
|
|
icond |= G_INPUT_READ;
|
|
|
|
if (condition & G_IO_OUT)
|
|
|
|
icond |= G_INPUT_WRITE;
|
|
|
|
|
2000-12-04 17:57:18 -05:00
|
|
|
if (rec->condition & icond)
|
|
|
|
rec->function(rec->data, source, icond);
|
2000-04-26 04:03:38 -04:00
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2000-12-04 17:57:18 -05:00
|
|
|
int g_input_add_full(GIOChannel *source, int priority, int condition,
|
2000-10-01 18:12:01 -04:00
|
|
|
GInputFunction function, void *data)
|
2000-04-26 04:03:38 -04:00
|
|
|
{
|
|
|
|
IRSSI_INPUT_REC *rec;
|
|
|
|
unsigned int result;
|
2000-08-14 20:22:08 -04:00
|
|
|
GIOCondition cond;
|
2000-04-26 04:03:38 -04:00
|
|
|
|
|
|
|
rec = g_new(IRSSI_INPUT_REC, 1);
|
|
|
|
rec->condition = condition;
|
|
|
|
rec->function = function;
|
|
|
|
rec->data = data;
|
|
|
|
|
2000-11-23 16:40:07 -05:00
|
|
|
cond = (GIOCondition) (G_IO_ERR|G_IO_HUP|G_IO_NVAL);
|
2000-04-26 04:03:38 -04:00
|
|
|
if (condition & G_INPUT_READ)
|
2000-08-14 20:22:08 -04:00
|
|
|
cond |= G_IO_IN|G_IO_PRI;
|
2000-04-26 04:03:38 -04:00
|
|
|
if (condition & G_INPUT_WRITE)
|
|
|
|
cond |= G_IO_OUT;
|
|
|
|
|
2000-12-04 17:57:18 -05:00
|
|
|
result = g_io_add_watch_full(source, priority, cond,
|
2000-04-26 04:03:38 -04:00
|
|
|
irssi_io_invoke, rec, g_free);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2000-12-04 17:57:18 -05:00
|
|
|
int g_input_add(GIOChannel *source, int condition,
|
2000-10-01 18:12:01 -04:00
|
|
|
GInputFunction function, void *data)
|
|
|
|
{
|
|
|
|
return g_input_add_full(source, G_PRIORITY_DEFAULT, condition,
|
|
|
|
function, data);
|
|
|
|
}
|
|
|
|
|
2008-04-25 04:42:47 -04:00
|
|
|
/* easy way to bypass glib polling of io channel internal buffer */
|
|
|
|
int g_input_add_poll(int fd, int priority, int condition,
|
|
|
|
GInputFunction function, void *data)
|
|
|
|
{
|
|
|
|
GIOChannel *source = g_io_channel_unix_new(fd);
|
|
|
|
int ret = g_input_add_full(source, priority, condition, function, data);
|
|
|
|
g_io_channel_unref(source);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2000-11-30 18:12:42 -05:00
|
|
|
int g_timeval_cmp(const GTimeVal *tv1, const GTimeVal *tv2)
|
|
|
|
{
|
|
|
|
if (tv1->tv_sec < tv2->tv_sec)
|
|
|
|
return -1;
|
|
|
|
if (tv1->tv_sec > tv2->tv_sec)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
return tv1->tv_usec < tv2->tv_usec ? -1 :
|
|
|
|
tv1->tv_usec > tv2->tv_usec ? 1 : 0;
|
|
|
|
}
|
|
|
|
|
2000-04-26 04:03:38 -04:00
|
|
|
long get_timeval_diff(const GTimeVal *tv1, const GTimeVal *tv2)
|
|
|
|
{
|
|
|
|
long secs, usecs;
|
|
|
|
|
|
|
|
secs = tv1->tv_sec - tv2->tv_sec;
|
|
|
|
usecs = tv1->tv_usec - tv2->tv_usec;
|
|
|
|
if (usecs < 0) {
|
|
|
|
usecs += 1000000;
|
|
|
|
secs--;
|
|
|
|
}
|
|
|
|
usecs = usecs/1000 + secs * 1000;
|
|
|
|
|
|
|
|
return usecs;
|
|
|
|
}
|
|
|
|
|
|
|
|
int find_substr(const char *list, const char *item)
|
|
|
|
{
|
|
|
|
const char *ptr;
|
|
|
|
|
|
|
|
g_return_val_if_fail(list != NULL, FALSE);
|
|
|
|
g_return_val_if_fail(item != NULL, FALSE);
|
|
|
|
|
|
|
|
if (*item == '\0')
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
for (;;) {
|
2002-01-27 15:45:59 -05:00
|
|
|
while (i_isspace(*list)) list++;
|
2000-04-26 04:03:38 -04:00
|
|
|
if (*list == '\0') break;
|
|
|
|
|
|
|
|
ptr = strchr(list, ' ');
|
|
|
|
if (ptr == NULL) ptr = list+strlen(list);
|
|
|
|
|
2014-06-10 12:06:19 -04:00
|
|
|
if (g_ascii_strncasecmp(list, item, ptr-list) == 0 &&
|
2000-07-16 16:18:05 -04:00
|
|
|
item[ptr-list] == '\0')
|
2000-04-26 04:03:38 -04:00
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
list = ptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
int strarray_find(char **array, const char *item)
|
|
|
|
{
|
|
|
|
char **tmp;
|
|
|
|
int index;
|
|
|
|
|
2016-03-13 17:04:59 -04:00
|
|
|
g_return_val_if_fail(array != NULL, -1);
|
|
|
|
g_return_val_if_fail(item != NULL, -1);
|
2000-04-26 04:03:38 -04:00
|
|
|
|
|
|
|
index = 0;
|
|
|
|
for (tmp = array; *tmp != NULL; tmp++, index++) {
|
2014-06-10 12:06:19 -04:00
|
|
|
if (g_ascii_strcasecmp(*tmp, item) == 0)
|
2000-04-26 04:03:38 -04:00
|
|
|
return index;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
GSList *gslist_find_string(GSList *list, const char *key)
|
|
|
|
{
|
2014-07-07 16:26:04 -04:00
|
|
|
for (; list != NULL; list = list->next)
|
2015-04-07 21:39:05 -04:00
|
|
|
if (g_strcmp0(list->data, key) == 0) return list;
|
2000-04-26 04:03:38 -04:00
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
GSList *gslist_find_icase_string(GSList *list, const char *key)
|
|
|
|
{
|
2014-07-07 16:26:04 -04:00
|
|
|
for (; list != NULL; list = list->next)
|
2014-06-10 12:06:19 -04:00
|
|
|
if (g_ascii_strcasecmp(list->data, key) == 0) return list;
|
2000-04-26 04:03:38 -04:00
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2000-08-26 11:39:44 -04:00
|
|
|
void *gslist_foreach_find(GSList *list, FOREACH_FIND_FUNC func, const void *data)
|
2000-04-26 04:03:38 -04:00
|
|
|
{
|
|
|
|
void *ret;
|
|
|
|
|
|
|
|
while (list != NULL) {
|
2000-08-26 11:39:44 -04:00
|
|
|
ret = func(list->data, (void *) data);
|
2000-04-26 04:03:38 -04:00
|
|
|
if (ret != NULL) return ret;
|
|
|
|
|
|
|
|
list = list->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2015-02-11 18:07:22 -05:00
|
|
|
void gslist_free_full (GSList *list, GDestroyNotify free_func)
|
|
|
|
{
|
|
|
|
GSList *tmp;
|
|
|
|
|
2015-09-02 16:40:10 -04:00
|
|
|
if (list == NULL)
|
2015-02-11 18:07:22 -05:00
|
|
|
return;
|
|
|
|
|
|
|
|
for (tmp = list; tmp != NULL; tmp = tmp->next)
|
|
|
|
free_func(tmp->data);
|
|
|
|
|
|
|
|
g_slist_free(list);
|
|
|
|
}
|
|
|
|
|
|
|
|
GSList *gslist_remove_string (GSList *list, const char *str)
|
|
|
|
{
|
|
|
|
GSList *l;
|
|
|
|
|
|
|
|
l = g_slist_find_custom(list, str, (GCompareFunc) g_strcmp0);
|
|
|
|
if (l != NULL)
|
|
|
|
return g_slist_remove_link(list, l);
|
|
|
|
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
|
2000-06-17 21:18:12 -04:00
|
|
|
/* `list' contains pointer to structure with a char* to string. */
|
|
|
|
char *gslistptr_to_string(GSList *list, int offset, const char *delimiter)
|
2000-04-26 04:03:38 -04:00
|
|
|
{
|
|
|
|
GString *str;
|
|
|
|
char **data, *ret;
|
|
|
|
|
|
|
|
str = g_string_new(NULL);
|
|
|
|
while (list != NULL) {
|
|
|
|
data = G_STRUCT_MEMBER_P(list->data, offset);
|
|
|
|
|
|
|
|
if (str->len != 0) g_string_append(str, delimiter);
|
|
|
|
g_string_append(str, *data);
|
|
|
|
list = list->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = str->str;
|
|
|
|
g_string_free(str, FALSE);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2000-06-17 21:18:12 -04:00
|
|
|
/* `list' contains char* */
|
|
|
|
char *gslist_to_string(GSList *list, const char *delimiter)
|
|
|
|
{
|
|
|
|
GString *str;
|
|
|
|
char *ret;
|
|
|
|
|
|
|
|
str = g_string_new(NULL);
|
|
|
|
while (list != NULL) {
|
|
|
|
if (str->len != 0) g_string_append(str, delimiter);
|
|
|
|
g_string_append(str, list->data);
|
|
|
|
|
|
|
|
list = list->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = str->str;
|
|
|
|
g_string_free(str, FALSE);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void hash_save_key(char *key, void *value, GSList **list)
|
|
|
|
{
|
|
|
|
*list = g_slist_append(*list, key);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* save all keys in hash table to linked list - you shouldn't remove any
|
|
|
|
items while using this list, use g_slist_free() after you're done with it */
|
|
|
|
GSList *hashtable_get_keys(GHashTable *hash)
|
|
|
|
{
|
|
|
|
GSList *list;
|
|
|
|
|
|
|
|
list = NULL;
|
|
|
|
g_hash_table_foreach(hash, (GHFunc) hash_save_key, &list);
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
|
2000-04-26 04:03:38 -04:00
|
|
|
GList *glist_find_string(GList *list, const char *key)
|
|
|
|
{
|
2014-07-07 16:26:04 -04:00
|
|
|
for (; list != NULL; list = list->next)
|
2015-04-07 21:39:05 -04:00
|
|
|
if (g_strcmp0(list->data, key) == 0) return list;
|
2000-04-26 04:03:38 -04:00
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
GList *glist_find_icase_string(GList *list, const char *key)
|
|
|
|
{
|
2014-07-07 16:26:04 -04:00
|
|
|
for (; list != NULL; list = list->next)
|
2014-06-10 12:06:19 -04:00
|
|
|
if (g_ascii_strcasecmp(list->data, key) == 0) return list;
|
2000-04-26 04:03:38 -04:00
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
char *stristr(const char *data, const char *key)
|
|
|
|
{
|
2000-11-06 20:25:46 -05:00
|
|
|
const char *max;
|
|
|
|
int keylen, datalen, pos;
|
2000-04-26 04:03:38 -04:00
|
|
|
|
|
|
|
keylen = strlen(key);
|
|
|
|
datalen = strlen(data);
|
|
|
|
|
2000-11-29 20:14:49 -05:00
|
|
|
if (keylen > datalen)
|
2000-04-26 04:03:38 -04:00
|
|
|
return NULL;
|
2000-11-29 20:14:49 -05:00
|
|
|
if (keylen == 0)
|
|
|
|
return (char *) data;
|
2000-04-26 04:03:38 -04:00
|
|
|
|
|
|
|
max = data+datalen-keylen;
|
2000-11-06 20:25:46 -05:00
|
|
|
pos = 0;
|
|
|
|
while (data <= max) {
|
|
|
|
if (key[pos] == '\0')
|
|
|
|
return (char *) data;
|
|
|
|
|
2002-01-27 15:45:59 -05:00
|
|
|
if (i_toupper(data[pos]) == i_toupper(key[pos]))
|
2000-11-06 20:25:46 -05:00
|
|
|
pos++;
|
|
|
|
else {
|
|
|
|
data++;
|
|
|
|
pos = 0;
|
|
|
|
}
|
2000-07-10 19:00:56 -04:00
|
|
|
}
|
2000-04-26 04:03:38 -04:00
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define isbound(c) \
|
|
|
|
((unsigned char) (c) < 128 && \
|
2002-01-27 15:45:59 -05:00
|
|
|
(i_isspace(c) || i_ispunct(c)))
|
2000-04-26 04:03:38 -04:00
|
|
|
|
2008-03-28 08:59:26 -04:00
|
|
|
static char *strstr_full_case(const char *data, const char *key, int icase)
|
2000-04-26 04:03:38 -04:00
|
|
|
{
|
2000-11-06 20:25:46 -05:00
|
|
|
const char *start, *max;
|
2001-03-03 12:34:35 -05:00
|
|
|
int keylen, datalen, pos, match;
|
2000-04-26 04:03:38 -04:00
|
|
|
|
|
|
|
keylen = strlen(key);
|
|
|
|
datalen = strlen(data);
|
|
|
|
|
2000-11-29 20:14:49 -05:00
|
|
|
if (keylen > datalen)
|
2000-04-26 04:03:38 -04:00
|
|
|
return NULL;
|
2000-11-29 20:14:49 -05:00
|
|
|
if (keylen == 0)
|
|
|
|
return (char *) data;
|
2000-04-26 04:03:38 -04:00
|
|
|
|
|
|
|
max = data+datalen-keylen;
|
2000-11-06 20:25:46 -05:00
|
|
|
start = data; pos = 0;
|
|
|
|
while (data <= max) {
|
|
|
|
if (key[pos] == '\0') {
|
|
|
|
if (data[pos] != '\0' && !isbound(data[pos])) {
|
|
|
|
data++;
|
|
|
|
pos = 0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
return (char *) data;
|
|
|
|
}
|
2000-04-26 04:03:38 -04:00
|
|
|
|
2002-01-27 15:45:59 -05:00
|
|
|
match = icase ? (i_toupper(data[pos]) == i_toupper(key[pos])) :
|
2001-03-03 12:34:35 -05:00
|
|
|
data[pos] == key[pos];
|
|
|
|
|
|
|
|
if (match && (pos != 0 || data == start || isbound(data[-1])))
|
2000-11-06 20:25:46 -05:00
|
|
|
pos++;
|
|
|
|
else {
|
|
|
|
data++;
|
|
|
|
pos = 0;
|
|
|
|
}
|
2000-04-26 04:03:38 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2001-03-03 12:34:35 -05:00
|
|
|
char *strstr_full(const char *data, const char *key)
|
|
|
|
{
|
|
|
|
return strstr_full_case(data, key, FALSE);
|
|
|
|
}
|
|
|
|
|
|
|
|
char *stristr_full(const char *data, const char *key)
|
|
|
|
{
|
|
|
|
return strstr_full_case(data, key, TRUE);
|
|
|
|
}
|
|
|
|
|
2000-08-12 11:50:50 -04:00
|
|
|
/* convert ~/ to $HOME */
|
2000-04-26 04:03:38 -04:00
|
|
|
char *convert_home(const char *path)
|
|
|
|
{
|
2002-10-19 13:48:10 -04:00
|
|
|
const char *home;
|
|
|
|
|
|
|
|
if (*path == '~' && (*(path+1) == '/' || *(path+1) == '\0')) {
|
|
|
|
home = g_get_home_dir();
|
|
|
|
if (home == NULL)
|
|
|
|
home = ".";
|
|
|
|
|
|
|
|
return g_strconcat(home, path+1, NULL);
|
|
|
|
} else {
|
|
|
|
return g_strdup(path);
|
|
|
|
}
|
2000-04-26 04:03:38 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
int g_istr_equal(gconstpointer v, gconstpointer v2)
|
|
|
|
{
|
2014-06-10 12:06:19 -04:00
|
|
|
return g_ascii_strcasecmp((const char *) v, (const char *) v2) == 0;
|
2000-04-26 04:03:38 -04:00
|
|
|
}
|
|
|
|
|
2000-06-17 09:16:42 -04:00
|
|
|
int g_istr_cmp(gconstpointer v, gconstpointer v2)
|
|
|
|
{
|
2014-06-10 12:06:19 -04:00
|
|
|
return g_ascii_strcasecmp((const char *) v, (const char *) v2);
|
2000-06-17 09:16:42 -04:00
|
|
|
}
|
|
|
|
|
2016-02-17 17:21:38 -05:00
|
|
|
guint g_istr_hash(gconstpointer v)
|
2000-04-26 04:03:38 -04:00
|
|
|
{
|
2016-02-17 17:21:38 -05:00
|
|
|
const signed char *p;
|
|
|
|
guint32 h = 5381;
|
|
|
|
|
|
|
|
for (p = v; *p != '\0'; p++)
|
|
|
|
h = (h << 5) + h + g_ascii_toupper(*p);
|
2000-04-26 04:03:38 -04:00
|
|
|
|
2016-02-17 17:21:38 -05:00
|
|
|
return h;
|
2000-04-26 04:03:38 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Find `mask' from `data', you can use * and ? wildcards. */
|
|
|
|
int match_wildcards(const char *cmask, const char *data)
|
|
|
|
{
|
|
|
|
char *mask, *newmask, *p1, *p2;
|
|
|
|
int ret;
|
|
|
|
|
2000-05-09 07:42:42 -04:00
|
|
|
newmask = mask = g_strdup(cmask);
|
2000-04-26 04:03:38 -04:00
|
|
|
for (; *mask != '\0' && *data != '\0'; mask++) {
|
2000-06-01 13:17:45 -04:00
|
|
|
if (*mask != '*') {
|
2002-01-27 15:45:59 -05:00
|
|
|
if (*mask != '?' && i_toupper(*mask) != i_toupper(*data))
|
2000-06-01 13:17:45 -04:00
|
|
|
break;
|
|
|
|
|
2000-04-26 04:03:38 -04:00
|
|
|
data++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (*mask == '?' || *mask == '*') mask++;
|
|
|
|
if (*mask == '\0') {
|
|
|
|
data += strlen(data);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
p1 = strchr(mask, '*');
|
|
|
|
p2 = strchr(mask, '?');
|
|
|
|
if (p1 == NULL || (p2 < p1 && p2 != NULL)) p1 = p2;
|
|
|
|
|
|
|
|
if (p1 != NULL) *p1 = '\0';
|
|
|
|
|
|
|
|
data = stristr(data, mask);
|
|
|
|
if (data == NULL) break;
|
|
|
|
|
|
|
|
data += strlen(mask);
|
|
|
|
mask += strlen(mask)-1;
|
|
|
|
|
|
|
|
if (p1 != NULL) *p1 = p1 == p2 ? '?' : '*';
|
|
|
|
}
|
|
|
|
|
2000-10-13 17:47:32 -04:00
|
|
|
while (*mask == '*') mask++;
|
|
|
|
|
2000-04-26 04:03:38 -04:00
|
|
|
ret = data != NULL && *data == '\0' && *mask == '\0';
|
2000-05-25 07:30:47 -04:00
|
|
|
g_free(newmask);
|
2000-04-26 04:03:38 -04:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Return TRUE if all characters in `str' are numbers.
|
|
|
|
Stop when `end_char' is found from string. */
|
|
|
|
int is_numeric(const char *str, char end_char)
|
|
|
|
{
|
|
|
|
g_return_val_if_fail(str != NULL, FALSE);
|
|
|
|
|
2000-07-23 07:54:54 -04:00
|
|
|
if (*str == '\0' || *str == end_char)
|
|
|
|
return FALSE;
|
|
|
|
|
2000-04-26 04:03:38 -04:00
|
|
|
while (*str != '\0' && *str != end_char) {
|
2002-01-27 15:45:59 -05:00
|
|
|
if (!i_isdigit(*str)) return FALSE;
|
2000-04-26 04:03:38 -04:00
|
|
|
str++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* replace all `from' chars in string to `to' chars. returns `str' */
|
|
|
|
char *replace_chars(char *str, char from, char to)
|
|
|
|
{
|
|
|
|
char *p;
|
|
|
|
|
|
|
|
for (p = str; *p != '\0'; p++) {
|
|
|
|
if (*p == from) *p = to;
|
|
|
|
}
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
int octal2dec(int octal)
|
|
|
|
{
|
|
|
|
int dec, n;
|
|
|
|
|
|
|
|
dec = 0; n = 1;
|
|
|
|
while (octal != 0) {
|
|
|
|
dec += n*(octal%10);
|
|
|
|
octal /= 10; n *= 8;
|
|
|
|
}
|
|
|
|
|
|
|
|
return dec;
|
|
|
|
}
|
|
|
|
|
|
|
|
int dec2octal(int decimal)
|
|
|
|
{
|
|
|
|
int octal, pos;
|
|
|
|
|
|
|
|
octal = 0; pos = 0;
|
|
|
|
while (decimal > 0) {
|
|
|
|
octal += (decimal & 7)*(pos == 0 ? 1 : pos);
|
|
|
|
decimal /= 8;
|
|
|
|
pos += 10;
|
|
|
|
}
|
|
|
|
|
|
|
|
return octal;
|
|
|
|
}
|
2000-06-30 15:54:34 -04:00
|
|
|
|
2002-11-21 12:48:40 -05:00
|
|
|
/* string -> uoff_t */
|
|
|
|
uoff_t str_to_uofft(const char *str)
|
|
|
|
{
|
2016-06-05 10:54:20 -04:00
|
|
|
#ifdef UOFF_T_LONG_LONG
|
|
|
|
return (uoff_t)strtoull(str, NULL, 10);
|
|
|
|
#else
|
|
|
|
return (uoff_t)strtoul(str, NULL, 10);
|
|
|
|
#endif
|
2002-11-21 12:48:40 -05:00
|
|
|
}
|
|
|
|
|
2000-06-30 15:54:34 -04:00
|
|
|
/* convert all low-ascii (<32) to ^<A..> combinations */
|
2008-03-02 20:57:50 -05:00
|
|
|
char *show_lowascii(const char *str)
|
2000-06-30 15:54:34 -04:00
|
|
|
{
|
2008-03-02 20:57:50 -05:00
|
|
|
char *ret, *p;
|
2000-06-30 15:54:34 -04:00
|
|
|
|
2008-03-02 20:57:50 -05:00
|
|
|
ret = p = g_malloc(strlen(str)*2+1);
|
|
|
|
while (*str != '\0') {
|
|
|
|
if ((unsigned char) *str >= 32)
|
|
|
|
*p++ = *str;
|
2000-06-30 15:54:34 -04:00
|
|
|
else {
|
|
|
|
*p++ = '^';
|
2008-03-02 20:57:50 -05:00
|
|
|
*p++ = *str + 'A'-1;
|
2000-06-30 15:54:34 -04:00
|
|
|
}
|
2008-03-02 20:57:50 -05:00
|
|
|
str++;
|
2000-06-30 15:54:34 -04:00
|
|
|
}
|
|
|
|
*p = '\0';
|
|
|
|
|
2008-03-02 20:57:50 -05:00
|
|
|
return ret;
|
2000-06-30 15:54:34 -04:00
|
|
|
}
|
|
|
|
|
2001-01-14 13:16:39 -05:00
|
|
|
/* Get time in human readable form with localtime() + asctime() */
|
|
|
|
char *my_asctime(time_t t)
|
|
|
|
{
|
|
|
|
struct tm *tm;
|
|
|
|
char *str;
|
|
|
|
int len;
|
|
|
|
|
|
|
|
tm = localtime(&t);
|
|
|
|
str = g_strdup(asctime(tm));
|
|
|
|
|
|
|
|
len = strlen(str);
|
|
|
|
if (len > 0) str[len-1] = '\0';
|
|
|
|
return str;
|
|
|
|
}
|
2001-01-28 10:43:38 -05:00
|
|
|
|
|
|
|
/* Returns number of columns needed to print items.
|
|
|
|
save_column_widths is filled with length of each column. */
|
|
|
|
int get_max_column_count(GSList *items, COLUMN_LEN_FUNC len_func,
|
2001-02-09 23:54:09 -05:00
|
|
|
int max_width, int max_columns,
|
|
|
|
int item_extra, int item_min_size,
|
2001-01-28 10:43:38 -05:00
|
|
|
int **save_column_widths, int *rows)
|
|
|
|
{
|
2001-01-28 11:37:13 -05:00
|
|
|
GSList *tmp;
|
2001-01-28 10:43:38 -05:00
|
|
|
int **columns, *columns_width, *columns_rows;
|
|
|
|
int item_pos, items_count;
|
2001-05-21 19:46:48 -04:00
|
|
|
int ret, len, max_len, n, col;
|
2001-01-28 10:43:38 -05:00
|
|
|
|
|
|
|
items_count = g_slist_length(items);
|
2001-02-02 17:09:11 -05:00
|
|
|
if (items_count == 0) {
|
|
|
|
*save_column_widths = NULL;
|
|
|
|
*rows = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
2001-01-28 10:43:38 -05:00
|
|
|
|
2001-02-09 23:54:09 -05:00
|
|
|
len = max_width/(item_extra+item_min_size);
|
2001-03-03 16:01:18 -05:00
|
|
|
if (len <= 0) len = 1;
|
2001-02-09 23:54:09 -05:00
|
|
|
if (max_columns <= 0 || len < max_columns)
|
|
|
|
max_columns = len;
|
|
|
|
|
2001-01-28 10:43:38 -05:00
|
|
|
columns = g_new0(int *, max_columns);
|
|
|
|
columns_width = g_new0(int, max_columns);
|
|
|
|
columns_rows = g_new0(int, max_columns);
|
|
|
|
|
|
|
|
for (n = 1; n < max_columns; n++) {
|
|
|
|
columns[n] = g_new0(int, n+1);
|
2001-01-28 11:37:13 -05:00
|
|
|
columns_rows[n] = items_count <= n+1 ? 1 :
|
2001-01-28 10:43:38 -05:00
|
|
|
(items_count+n)/(n+1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* for each possible column count, save the column widths and
|
|
|
|
find the biggest column count that fits to screen. */
|
2001-05-21 19:46:48 -04:00
|
|
|
item_pos = 0; max_len = 0;
|
2001-01-28 11:37:13 -05:00
|
|
|
for (tmp = items; tmp != NULL; tmp = tmp->next) {
|
2001-05-21 19:46:48 -04:00
|
|
|
len = item_extra+len_func(tmp->data);
|
|
|
|
if (max_len < len)
|
|
|
|
max_len = len;
|
|
|
|
|
2001-01-28 10:43:38 -05:00
|
|
|
for (n = 1; n < max_columns; n++) {
|
|
|
|
if (columns_width[n] > max_width)
|
|
|
|
continue; /* too wide */
|
|
|
|
|
|
|
|
col = item_pos/columns_rows[n];
|
|
|
|
if (columns[n][col] < len) {
|
|
|
|
columns_width[n] += len-columns[n][col];
|
|
|
|
columns[n][col] = len;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
item_pos++;
|
|
|
|
}
|
|
|
|
|
2001-06-11 11:05:08 -04:00
|
|
|
for (n = max_columns-1; n >= 1; n--) {
|
2001-01-28 11:37:13 -05:00
|
|
|
if (columns_width[n] <= max_width &&
|
|
|
|
columns[n][n] > 0)
|
2001-01-28 10:43:38 -05:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
ret = n+1;
|
|
|
|
|
|
|
|
*save_column_widths = g_new(int, ret);
|
2001-01-28 11:37:13 -05:00
|
|
|
if (ret == 1) {
|
2001-05-21 19:46:48 -04:00
|
|
|
**save_column_widths = max_len;
|
2001-01-28 11:37:13 -05:00
|
|
|
*rows = 1;
|
|
|
|
} else {
|
|
|
|
memcpy(*save_column_widths, columns[ret-1], sizeof(int)*ret);
|
|
|
|
*rows = columns_rows[ret-1];
|
|
|
|
}
|
2001-01-28 10:43:38 -05:00
|
|
|
|
|
|
|
for (n = 1; n < max_columns; n++)
|
|
|
|
g_free(columns[n]);
|
|
|
|
g_free(columns_width);
|
|
|
|
g_free(columns_rows);
|
|
|
|
g_free(columns);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Return a column sorted copy of a list. */
|
|
|
|
GSList *columns_sort_list(GSList *list, int rows)
|
|
|
|
{
|
|
|
|
GSList *tmp, *sorted;
|
|
|
|
int row, skip;
|
|
|
|
|
2001-02-02 17:09:11 -05:00
|
|
|
if (list == NULL || rows == 0)
|
|
|
|
return list;
|
|
|
|
|
2001-01-28 10:43:38 -05:00
|
|
|
sorted = NULL;
|
|
|
|
|
|
|
|
for (row = 0; row < rows; row++) {
|
|
|
|
tmp = g_slist_nth(list, row);
|
|
|
|
skip = 1;
|
|
|
|
for (; tmp != NULL; tmp = tmp->next) {
|
|
|
|
if (--skip == 0) {
|
|
|
|
skip = rows;
|
|
|
|
sorted = g_slist_append(sorted, tmp->data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
g_return_val_if_fail(g_slist_length(sorted) ==
|
|
|
|
g_slist_length(list), sorted);
|
|
|
|
return sorted;
|
|
|
|
}
|
2001-08-08 16:00:25 -04:00
|
|
|
|
|
|
|
/* Expand escape string, the first character in data should be the
|
|
|
|
one after '\'. Returns the expanded character or -1 if error. */
|
|
|
|
int expand_escape(const char **data)
|
|
|
|
{
|
|
|
|
char digit[4];
|
|
|
|
|
|
|
|
switch (**data) {
|
|
|
|
case 't':
|
|
|
|
return '\t';
|
|
|
|
case 'r':
|
|
|
|
return '\r';
|
|
|
|
case 'n':
|
|
|
|
return '\n';
|
|
|
|
case 'e':
|
|
|
|
return 27; /* ESC */
|
|
|
|
|
|
|
|
case 'x':
|
|
|
|
/* hex digit */
|
2002-02-11 22:04:34 -05:00
|
|
|
if (!i_isxdigit((*data)[1]) || !i_isxdigit((*data)[2]))
|
2001-08-08 16:00:25 -04:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
digit[0] = (*data)[1];
|
|
|
|
digit[1] = (*data)[2];
|
|
|
|
digit[2] = '\0';
|
|
|
|
*data += 2;
|
|
|
|
return strtol(digit, NULL, 16);
|
|
|
|
case 'c':
|
|
|
|
/* control character (\cA = ^A) */
|
|
|
|
(*data)++;
|
2002-01-27 15:45:59 -05:00
|
|
|
return i_toupper(**data) - 64;
|
2010-11-17 15:41:14 -05:00
|
|
|
case '0': case '1': case '2': case '3':
|
|
|
|
case '4': case '5': case '6': case '7':
|
2001-08-08 16:00:25 -04:00
|
|
|
/* octal */
|
2010-11-17 15:41:14 -05:00
|
|
|
digit[1] = digit[2] = digit[3] = '\0';
|
2001-08-08 16:00:25 -04:00
|
|
|
digit[0] = (*data)[0];
|
2010-11-17 15:41:14 -05:00
|
|
|
if ((*data)[1] >= '0' && (*data)[1] <= '7') {
|
|
|
|
++*data;
|
|
|
|
digit[1] = **data;
|
|
|
|
if ((*data)[1] >= '0' && (*data)[1] <= '7') {
|
|
|
|
++*data;
|
|
|
|
digit[2] = **data;
|
|
|
|
}
|
|
|
|
}
|
2001-08-08 16:00:25 -04:00
|
|
|
return strtol(digit, NULL, 8);
|
2010-11-17 15:41:14 -05:00
|
|
|
default:
|
|
|
|
return -1;
|
2001-08-08 16:00:25 -04:00
|
|
|
}
|
|
|
|
}
|
2002-07-16 12:20:10 -04:00
|
|
|
|
|
|
|
/* Escape all '"', "'" and '\' chars with '\' */
|
|
|
|
char *escape_string(const char *str)
|
|
|
|
{
|
|
|
|
char *ret, *p;
|
|
|
|
|
|
|
|
p = ret = g_malloc(strlen(str)*2+1);
|
|
|
|
while (*str != '\0') {
|
|
|
|
if (*str == '"' || *str == '\'' || *str == '\\')
|
|
|
|
*p++ = '\\';
|
|
|
|
*p++ = *str++;
|
|
|
|
}
|
|
|
|
*p = '\0';
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
2002-12-28 09:20:24 -05:00
|
|
|
|
|
|
|
int nearest_power(int num)
|
|
|
|
{
|
|
|
|
int n = 1;
|
|
|
|
|
|
|
|
while (n < num) n <<= 1;
|
|
|
|
return n;
|
|
|
|
}
|
2002-12-28 12:54:13 -05:00
|
|
|
|
|
|
|
int parse_time_interval(const char *time, int *msecs)
|
|
|
|
{
|
|
|
|
const char *desc;
|
2009-04-03 18:09:17 -04:00
|
|
|
int number, sign, len, ret, digits;
|
2002-12-28 12:54:13 -05:00
|
|
|
|
|
|
|
*msecs = 0;
|
|
|
|
|
2003-11-16 12:26:43 -05:00
|
|
|
/* max. return value is around 24 days */
|
2009-04-03 18:09:17 -04:00
|
|
|
number = 0; sign = 1; ret = TRUE; digits = FALSE;
|
|
|
|
while (i_isspace(*time))
|
|
|
|
time++;
|
|
|
|
if (*time == '-') {
|
|
|
|
sign = -sign;
|
|
|
|
time++;
|
|
|
|
while (i_isspace(*time))
|
2005-09-10 08:51:43 -04:00
|
|
|
time++;
|
2009-04-03 18:09:17 -04:00
|
|
|
}
|
|
|
|
for (;;) {
|
2002-12-28 12:54:13 -05:00
|
|
|
if (i_isdigit(*time)) {
|
|
|
|
number = number*10 + (*time - '0');
|
|
|
|
time++;
|
2009-04-03 18:09:17 -04:00
|
|
|
digits = TRUE;
|
2002-12-28 12:54:13 -05:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2009-04-03 18:09:17 -04:00
|
|
|
if (!digits)
|
|
|
|
return FALSE;
|
|
|
|
|
2002-12-28 12:54:13 -05:00
|
|
|
/* skip punctuation */
|
2009-04-03 18:09:17 -04:00
|
|
|
while (*time != '\0' && i_ispunct(*time) && *time != '-')
|
2002-12-28 12:54:13 -05:00
|
|
|
time++;
|
|
|
|
|
|
|
|
/* get description */
|
|
|
|
for (len = 0, desc = time; i_isalpha(*time); time++)
|
|
|
|
len++;
|
|
|
|
|
2009-04-03 18:09:17 -04:00
|
|
|
while (i_isspace(*time))
|
|
|
|
time++;
|
|
|
|
|
2002-12-28 12:54:13 -05:00
|
|
|
if (len == 0) {
|
2009-04-03 18:09:17 -04:00
|
|
|
if (*time != '\0')
|
|
|
|
return FALSE;
|
2003-01-08 15:54:36 -05:00
|
|
|
*msecs += number * 1000; /* assume seconds */
|
2005-09-10 08:51:43 -04:00
|
|
|
*msecs *= sign;
|
2003-11-16 11:22:07 -05:00
|
|
|
return TRUE;
|
2002-12-28 12:54:13 -05:00
|
|
|
}
|
|
|
|
|
2008-03-09 08:17:55 -04:00
|
|
|
if (g_ascii_strncasecmp(desc, "days", len) == 0) {
|
2003-11-16 12:26:43 -05:00
|
|
|
if (number > 24) {
|
|
|
|
/* would overflow */
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
*msecs += number * 1000*3600*24;
|
2008-03-09 08:17:55 -04:00
|
|
|
} else if (g_ascii_strncasecmp(desc, "hours", len) == 0)
|
2004-11-04 10:08:57 -05:00
|
|
|
*msecs += number * 1000*3600;
|
2008-03-09 08:17:55 -04:00
|
|
|
else if (g_ascii_strncasecmp(desc, "minutes", len) == 0 ||
|
|
|
|
g_ascii_strncasecmp(desc, "mins", len) == 0)
|
2002-12-28 12:54:13 -05:00
|
|
|
*msecs += number * 1000*60;
|
2008-03-09 08:17:55 -04:00
|
|
|
else if (g_ascii_strncasecmp(desc, "seconds", len) == 0 ||
|
|
|
|
g_ascii_strncasecmp(desc, "secs", len) == 0)
|
2002-12-28 12:54:13 -05:00
|
|
|
*msecs += number * 1000;
|
2008-03-09 08:17:55 -04:00
|
|
|
else if (g_ascii_strncasecmp(desc, "milliseconds", len) == 0 ||
|
|
|
|
g_ascii_strncasecmp(desc, "millisecs", len) == 0 ||
|
|
|
|
g_ascii_strncasecmp(desc, "mseconds", len) == 0 ||
|
|
|
|
g_ascii_strncasecmp(desc, "msecs", len) == 0)
|
2002-12-28 12:54:13 -05:00
|
|
|
*msecs += number;
|
2003-11-16 11:22:07 -05:00
|
|
|
else {
|
|
|
|
ret = FALSE;
|
|
|
|
}
|
2002-12-28 12:54:13 -05:00
|
|
|
|
|
|
|
/* skip punctuation */
|
2009-04-03 18:09:17 -04:00
|
|
|
while (*time != '\0' && i_ispunct(*time) && *time != '-')
|
2002-12-28 12:54:13 -05:00
|
|
|
time++;
|
|
|
|
|
|
|
|
if (*time == '\0')
|
|
|
|
break;
|
|
|
|
|
|
|
|
number = 0;
|
2009-04-03 18:09:17 -04:00
|
|
|
digits = FALSE;
|
2002-12-28 12:54:13 -05:00
|
|
|
}
|
|
|
|
|
2005-09-10 08:51:43 -04:00
|
|
|
*msecs *= sign;
|
2003-11-16 11:22:07 -05:00
|
|
|
return ret;
|
2002-12-28 12:54:13 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
int parse_size(const char *size, int *bytes)
|
|
|
|
{
|
|
|
|
const char *desc;
|
|
|
|
int number, len;
|
|
|
|
|
|
|
|
*bytes = 0;
|
|
|
|
|
|
|
|
/* max. return value is about 1.6 years */
|
|
|
|
number = 0;
|
|
|
|
while (*size != '\0') {
|
|
|
|
if (i_isdigit(*size)) {
|
|
|
|
number = number*10 + (*size - '0');
|
|
|
|
size++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* skip punctuation */
|
|
|
|
while (*size != '\0' && i_ispunct(*size))
|
|
|
|
size++;
|
|
|
|
|
|
|
|
/* get description */
|
|
|
|
for (len = 0, desc = size; i_isalpha(*size); size++)
|
|
|
|
len++;
|
|
|
|
|
|
|
|
if (len == 0) {
|
|
|
|
if (number == 0) {
|
|
|
|
/* "0" - allow it */
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
*bytes += number*1024; /* assume kilobytes */
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2008-03-09 08:17:55 -04:00
|
|
|
if (g_ascii_strncasecmp(desc, "gbytes", len) == 0)
|
2002-12-28 12:54:13 -05:00
|
|
|
*bytes += number * 1024*1024*1024;
|
2008-03-09 08:17:55 -04:00
|
|
|
if (g_ascii_strncasecmp(desc, "mbytes", len) == 0)
|
2002-12-28 12:54:13 -05:00
|
|
|
*bytes += number * 1024*1024;
|
2008-03-09 08:17:55 -04:00
|
|
|
if (g_ascii_strncasecmp(desc, "kbytes", len) == 0)
|
2002-12-28 12:54:13 -05:00
|
|
|
*bytes += number * 1024;
|
2008-03-09 08:17:55 -04:00
|
|
|
if (g_ascii_strncasecmp(desc, "bytes", len) == 0)
|
2002-12-28 12:54:13 -05:00
|
|
|
*bytes += number;
|
|
|
|
|
|
|
|
/* skip punctuation */
|
|
|
|
while (*size != '\0' && i_ispunct(*size))
|
|
|
|
size++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
2010-04-03 16:09:37 -04:00
|
|
|
|
|
|
|
char *ascii_strup(char *str)
|
|
|
|
{
|
|
|
|
char *s;
|
|
|
|
|
|
|
|
for (s = str; *s; s++)
|
|
|
|
*s = g_ascii_toupper (*s);
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
char *ascii_strdown(char *str)
|
|
|
|
{
|
|
|
|
char *s;
|
|
|
|
|
|
|
|
for (s = str; *s; s++)
|
|
|
|
*s = g_ascii_tolower (*s);
|
|
|
|
return str;
|
|
|
|
}
|
2014-06-13 00:39:02 -04:00
|
|
|
|
2014-10-11 12:47:39 -04:00
|
|
|
char **strsplit_len(const char *str, int len, gboolean onspace)
|
|
|
|
{
|
|
|
|
char **ret = g_new(char *, 1);
|
|
|
|
int n;
|
2015-11-09 04:46:40 -05:00
|
|
|
int offset;
|
2014-10-11 12:47:39 -04:00
|
|
|
|
2015-11-09 04:46:40 -05:00
|
|
|
for (n = 0; *str != '\0'; n++, str += offset) {
|
|
|
|
offset = MIN(len, strlen(str));
|
2015-11-09 04:33:08 -05:00
|
|
|
if (onspace && strlen(str) > len) {
|
2014-10-11 12:47:39 -04:00
|
|
|
/*
|
|
|
|
* Try to find a space to split on and leave
|
|
|
|
* the space on the previous line.
|
|
|
|
*/
|
|
|
|
int i;
|
Fix invalid reads in strsplit_len when splitting on spaces
The symptom for this one is randomly getting lines split before the last
word, even if there's no need for splitting. Also, this function is only
reached if recode is on, and iconv failed (for example, due to an
incorrect source charset). Thanks to vague for finding this and
providing valgrind logs.
The loop that looks for spaces tried to read backwards from the end of
the current line, with the end being determined by len. Assuming
strsplit_len() with len=400, this meant accessing str[399] in the first
iteration. For strings that don't need splitting, this means an invalid
read always.
If that invalid read happens to hit garbage that has a space character,
(len - offset) points after the end of string, which isn't a problem for
g_strndup() since it stops at the first null, and no splitting happens.
If the garbage doesn't have any spaces, it splits by the last word.
This commit avoids that loop entirely if (remaining_len > len). It also
changes the way it iterates over the string to be much less confusing.
2015-10-23 03:25:57 -04:00
|
|
|
for (i = len - 1; i > 0; i--) {
|
|
|
|
if (str[i] == ' ') {
|
2015-11-09 04:46:40 -05:00
|
|
|
offset = i;
|
2014-10-11 12:47:39 -04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-11-09 04:46:40 -05:00
|
|
|
ret[n] = g_strndup(str, offset);
|
2014-10-11 12:47:39 -04:00
|
|
|
ret = g_renew(char *, ret, n + 2);
|
|
|
|
}
|
2014-06-13 00:39:02 -04:00
|
|
|
ret[n] = NULL;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|