Merged with Irssi 0.8.6.
[silc.git] / apps / irssi / src / core / misc.c
1 /*
2  misc.c : irssi
3
4     Copyright (C) 1999 Timo Sirainen
5
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21 #include "module.h"
22 #include "misc.h"
23 #include "pidwait.h"
24
25 #include <errno.h>
26 #ifdef HAVE_REGEX_H
27 #  include <regex.h>
28 #endif
29
30 typedef struct {
31         int condition;
32         GInputFunction function;
33         void *data;
34 } IRSSI_INPUT_REC;
35
36 static int irssi_io_invoke(GIOChannel *source, GIOCondition condition,
37                            void *data)
38 {
39         IRSSI_INPUT_REC *rec = data;
40         int icond = 0;
41
42         if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) {
43                 /* error, we have to call the function.. */
44                 if (rec->condition & G_IO_IN)
45                         icond |= G_INPUT_READ;
46                 else
47                         icond |= G_INPUT_WRITE;
48         }
49
50         if (condition & (G_IO_IN | G_IO_PRI))
51                 icond |= G_INPUT_READ;
52         if (condition & G_IO_OUT)
53                 icond |= G_INPUT_WRITE;
54
55         if (rec->condition & icond)
56                 rec->function(rec->data, source, icond);
57
58         return TRUE;
59 }
60
61 int g_input_add_full(GIOChannel *source, int priority, int condition,
62                      GInputFunction function, void *data)
63 {
64         IRSSI_INPUT_REC *rec;
65         unsigned int result;
66         GIOCondition cond;
67
68         rec = g_new(IRSSI_INPUT_REC, 1);
69         rec->condition = condition;
70         rec->function = function;
71         rec->data = data;
72
73         cond = (GIOCondition) (G_IO_ERR|G_IO_HUP|G_IO_NVAL);
74         if (condition & G_INPUT_READ)
75                 cond |= G_IO_IN|G_IO_PRI;
76         if (condition & G_INPUT_WRITE)
77                 cond |= G_IO_OUT;
78
79         result = g_io_add_watch_full(source, priority, cond,
80                                      irssi_io_invoke, rec, g_free);
81
82         return result;
83 }
84
85 int g_input_add(GIOChannel *source, int condition,
86                 GInputFunction function, void *data)
87 {
88         return g_input_add_full(source, G_PRIORITY_DEFAULT, condition,
89                                 function, data);
90 }
91
92 int g_timeval_cmp(const GTimeVal *tv1, const GTimeVal *tv2)
93 {
94         if (tv1->tv_sec < tv2->tv_sec)
95                 return -1;
96         if (tv1->tv_sec > tv2->tv_sec)
97                 return 1;
98
99         return tv1->tv_usec < tv2->tv_usec ? -1 :
100                 tv1->tv_usec > tv2->tv_usec ? 1 : 0;
101 }
102
103 long get_timeval_diff(const GTimeVal *tv1, const GTimeVal *tv2)
104 {
105         long secs, usecs;
106
107         secs = tv1->tv_sec - tv2->tv_sec;
108         usecs = tv1->tv_usec - tv2->tv_usec;
109         if (usecs < 0) {
110                 usecs += 1000000;
111                 secs--;
112         }
113         usecs = usecs/1000 + secs * 1000;
114
115         return usecs;
116 }
117
118 int find_substr(const char *list, const char *item)
119 {
120         const char *ptr;
121
122         g_return_val_if_fail(list != NULL, FALSE);
123         g_return_val_if_fail(item != NULL, FALSE);
124
125         if (*item == '\0')
126                 return FALSE;
127
128         for (;;) {
129                 while (i_isspace(*list)) list++;
130                 if (*list == '\0') break;
131
132                 ptr = strchr(list, ' ');
133                 if (ptr == NULL) ptr = list+strlen(list);
134
135                 if (g_strncasecmp(list, item, ptr-list) == 0 &&
136                     item[ptr-list] == '\0')
137                         return TRUE;
138
139                 list = ptr;
140         }
141
142         return FALSE;
143 }
144
145 int strarray_length(char **array)
146 {
147         int len;
148
149         g_return_val_if_fail(array != NULL, 0);
150
151         len = 0;
152         while (*array) {
153                 len++;
154                 array++;
155         }
156         return len;
157 }
158
159 int strarray_find(char **array, const char *item)
160 {
161         char **tmp;
162         int index;
163
164         g_return_val_if_fail(array != NULL, 0);
165         g_return_val_if_fail(item != NULL, 0);
166
167         index = 0;
168         for (tmp = array; *tmp != NULL; tmp++, index++) {
169                 if (g_strcasecmp(*tmp, item) == 0)
170                         return index;
171         }
172
173         return -1;
174 }
175
176 int execute(const char *cmd)
177 {
178         char **args;
179 #ifndef WIN32
180         int pid;
181 #endif
182
183         g_return_val_if_fail(cmd != NULL, -1);
184
185 #ifndef WIN32
186         pid = fork();
187         if (pid == -1) return FALSE;
188         if (pid != 0) {
189                 pidwait_add(pid);
190                 return pid;
191         }
192
193         args = g_strsplit(cmd, " ", -1);
194         execvp(args[0], args);
195         g_strfreev(args);
196
197         _exit(99);
198         return -1;
199 #else
200         args = g_strsplit(cmd, " ", -1);
201         _spawnvp(_P_DETACH, args[0], args);
202         g_strfreev(args);
203         return 0;
204 #endif
205 }
206
207 GSList *gslist_find_string(GSList *list, const char *key)
208 {
209         for (list = list; list != NULL; list = list->next)
210                 if (strcmp(list->data, key) == 0) return list;
211
212         return NULL;
213 }
214
215 GSList *gslist_find_icase_string(GSList *list, const char *key)
216 {
217         for (list = list; list != NULL; list = list->next)
218                 if (g_strcasecmp(list->data, key) == 0) return list;
219
220         return NULL;
221 }
222
223 void *gslist_foreach_find(GSList *list, FOREACH_FIND_FUNC func, const void *data)
224 {
225         void *ret;
226
227         while (list != NULL) {
228                 ret = func(list->data, (void *) data);
229                 if (ret != NULL) return ret;
230
231                 list = list->next;
232         }
233
234         return NULL;
235 }
236
237 /* `list' contains pointer to structure with a char* to string. */
238 char *gslistptr_to_string(GSList *list, int offset, const char *delimiter)
239 {
240         GString *str;
241         char **data, *ret;
242
243         str = g_string_new(NULL);
244         while (list != NULL) {
245                 data = G_STRUCT_MEMBER_P(list->data, offset);
246
247                 if (str->len != 0) g_string_append(str, delimiter);
248                 g_string_append(str, *data);
249                 list = list->next;
250         }
251
252         ret = str->str;
253         g_string_free(str, FALSE);
254         return ret;
255 }
256
257 /* `list' contains char* */
258 char *gslist_to_string(GSList *list, const char *delimiter)
259 {
260         GString *str;
261         char *ret;
262
263         str = g_string_new(NULL);
264         while (list != NULL) {
265                 if (str->len != 0) g_string_append(str, delimiter);
266                 g_string_append(str, list->data);
267
268                 list = list->next;
269         }
270
271         ret = str->str;
272         g_string_free(str, FALSE);
273         return ret;
274 }
275
276 void hash_save_key(char *key, void *value, GSList **list)
277 {
278         *list = g_slist_append(*list, key);
279 }
280
281 /* save all keys in hash table to linked list - you shouldn't remove any
282    items while using this list, use g_slist_free() after you're done with it */
283 GSList *hashtable_get_keys(GHashTable *hash)
284 {
285         GSList *list;
286
287         list = NULL;
288         g_hash_table_foreach(hash, (GHFunc) hash_save_key, &list);
289         return list;
290 }
291
292 GList *glist_find_string(GList *list, const char *key)
293 {
294         for (list = list; list != NULL; list = list->next)
295                 if (strcmp(list->data, key) == 0) return list;
296
297         return NULL;
298 }
299
300 GList *glist_find_icase_string(GList *list, const char *key)
301 {
302         for (list = list; list != NULL; list = list->next)
303                 if (g_strcasecmp(list->data, key) == 0) return list;
304
305         return NULL;
306 }
307
308 char *stristr(const char *data, const char *key)
309 {
310         const char *max;
311         int keylen, datalen, pos;
312
313         keylen = strlen(key);
314         datalen = strlen(data);
315
316         if (keylen > datalen)
317                 return NULL;
318         if (keylen == 0)
319                 return (char *) data;
320
321         max = data+datalen-keylen;
322         pos = 0;
323         while (data <= max) {
324                 if (key[pos] == '\0')
325                         return (char *) data;
326
327                 if (i_toupper(data[pos]) == i_toupper(key[pos]))
328                         pos++;
329                 else {
330                         data++;
331                         pos = 0;
332                 }
333         }
334
335         return NULL;
336 }
337
338 #define isbound(c) \
339         ((unsigned char) (c) < 128 && \
340         (i_isspace(c) || i_ispunct(c)))
341
342 char *strstr_full_case(const char *data, const char *key, int icase)
343 {
344         const char *start, *max;
345         int keylen, datalen, pos, match;
346
347         keylen = strlen(key);
348         datalen = strlen(data);
349
350         if (keylen > datalen)
351                 return NULL;
352         if (keylen == 0)
353                 return (char *) data;
354
355         max = data+datalen-keylen;
356         start = data; pos = 0;
357         while (data <= max) {
358                 if (key[pos] == '\0') {
359                         if (data[pos] != '\0' && !isbound(data[pos])) {
360                                 data++;
361                                 pos = 0;
362                                 continue;
363                         }
364                         return (char *) data;
365                 }
366
367                 match = icase ? (i_toupper(data[pos]) == i_toupper(key[pos])) :
368                                  data[pos] == key[pos];
369
370                 if (match && (pos != 0 || data == start || isbound(data[-1])))
371                         pos++;
372                 else {
373                         data++;
374                         pos = 0;
375                 }
376         }
377
378         return NULL;
379 }
380
381 char *strstr_full(const char *data, const char *key)
382 {
383         return strstr_full_case(data, key, FALSE);
384 }
385
386 char *stristr_full(const char *data, const char *key)
387 {
388         return strstr_full_case(data, key, TRUE);
389 }
390
391 int regexp_match(const char *str, const char *regexp)
392 {
393 #ifdef HAVE_REGEX_H
394         regex_t preg;
395         int ret;
396
397         if (regcomp(&preg, regexp, REG_EXTENDED|REG_ICASE|REG_NOSUB) != 0)
398                 return 0;
399
400         ret = regexec(&preg, str, 0, NULL, 0);
401         regfree(&preg);
402
403         return ret == 0;
404 #else
405         return FALSE;
406 #endif
407 }
408
409 /* Create the directory and all it's parent directories */
410 int mkpath(const char *path, int mode)
411 {
412         struct stat statbuf;
413         const char *p;
414         char *dir;
415
416         g_return_val_if_fail(path != NULL, -1);
417
418         p = g_path_skip_root((char *) path);
419         if (p == NULL) {
420                 /* not a full path, maybe not what we wanted
421                    but continue anyway.. */
422                 p = path;
423         }
424         for (;;) {
425                 if (*p != G_DIR_SEPARATOR && *p != '\0') {
426                         p++;
427                         continue;
428                 }
429
430                 dir = g_strndup(path, (int) (p-path));
431                 if (stat(dir, &statbuf) != 0) {
432 #ifndef WIN32
433                         if (mkdir(dir, mode) == -1)
434 #else
435                         if (_mkdir(dir) == -1)
436 #endif
437                         {
438                                 g_free(dir);
439                                 return -1;
440                         }
441                 }
442                 g_free(dir);
443
444                 if (*p++ == '\0')
445                         break;
446         }
447
448         return 0;
449 }
450
451 /* convert ~/ to $HOME */
452 char *convert_home(const char *path)
453 {
454         const char *home;
455
456         if (*path == '~' && (*(path+1) == '/' || *(path+1) == '\0')) {
457                 home = g_get_home_dir();
458                 if (home == NULL)
459                         home = ".";
460
461                 return g_strconcat(home, path+1, NULL);
462         } else {
463                 return g_strdup(path);
464         }
465 }
466
467 int g_istr_equal(gconstpointer v, gconstpointer v2)
468 {
469         return g_strcasecmp((const char *) v, (const char *) v2) == 0;
470 }
471
472 int g_istr_cmp(gconstpointer v, gconstpointer v2)
473 {
474         return g_strcasecmp((const char *) v, (const char *) v2);
475 }
476
477 /* a char* hash function from ASU */
478 unsigned int g_istr_hash(gconstpointer v)
479 {
480         const char *s = (const char *) v;
481         unsigned int h = 0, g;
482
483         while (*s != '\0') {
484                 h = (h << 4) + i_toupper(*s);
485                 if ((g = h & 0xf0000000UL)) {
486                         h = h ^ (g >> 24);
487                         h = h ^ g;
488                 }
489                 s++;
490         }
491
492         return h /* % M */;
493 }
494
495 /* Find `mask' from `data', you can use * and ? wildcards. */
496 int match_wildcards(const char *cmask, const char *data)
497 {
498         char *mask, *newmask, *p1, *p2;
499         int ret;
500
501         newmask = mask = g_strdup(cmask);
502         for (; *mask != '\0' && *data != '\0'; mask++) {
503                 if (*mask != '*') {
504                         if (*mask != '?' && i_toupper(*mask) != i_toupper(*data))
505                                 break;
506
507                         data++;
508                         continue;
509                 }
510
511                 while (*mask == '?' || *mask == '*') mask++;
512                 if (*mask == '\0') {
513                         data += strlen(data);
514                         break;
515                 }
516
517                 p1 = strchr(mask, '*');
518                 p2 = strchr(mask, '?');
519                 if (p1 == NULL || (p2 < p1 && p2 != NULL)) p1 = p2;
520
521                 if (p1 != NULL) *p1 = '\0';
522
523                 data = stristr(data, mask);
524                 if (data == NULL) break;
525
526                 data += strlen(mask);
527                 mask += strlen(mask)-1;
528
529                 if (p1 != NULL) *p1 = p1 == p2 ? '?' : '*';
530         }
531
532         while (*mask == '*') mask++;
533
534         ret = data != NULL && *data == '\0' && *mask == '\0';
535         g_free(newmask);
536
537         return ret;
538 }
539
540 /* Return TRUE if all characters in `str' are numbers.
541    Stop when `end_char' is found from string. */
542 int is_numeric(const char *str, char end_char)
543 {
544         g_return_val_if_fail(str != NULL, FALSE);
545
546         if (*str == '\0' || *str == end_char)
547                 return FALSE;
548
549         while (*str != '\0' && *str != end_char) {
550                 if (!i_isdigit(*str)) return FALSE;
551                 str++;
552         }
553
554         return TRUE;
555 }
556
557 /* replace all `from' chars in string to `to' chars. returns `str' */
558 char *replace_chars(char *str, char from, char to)
559 {
560         char *p;
561
562         for (p = str; *p != '\0'; p++) {
563                 if (*p == from) *p = to;
564         }
565         return str;
566 }
567
568 int octal2dec(int octal)
569 {
570         int dec, n;
571
572         dec = 0; n = 1;
573         while (octal != 0) {
574                 dec += n*(octal%10);
575                 octal /= 10; n *= 8;
576         }
577
578         return dec;
579 }
580
581 int dec2octal(int decimal)
582 {
583         int octal, pos;
584
585         octal = 0; pos = 0;
586         while (decimal > 0) {
587                 octal += (decimal & 7)*(pos == 0 ? 1 : pos);
588                 decimal /= 8;
589                 pos += 10;
590         }
591
592         return octal;
593 }
594
595 /* string -> uoff_t */
596 uoff_t str_to_uofft(const char *str)
597 {
598         uoff_t ret;
599
600         ret = 0;
601         while (*str != '\0') {
602                 ret = ret*10 + (*str - '0');
603                 str++;
604         }
605
606         return ret;
607 }
608
609 /* convert all low-ascii (<32) to ^<A..> combinations */
610 char *show_lowascii(const char *channel)
611 {
612         char *str, *p;
613
614         str = p = g_malloc(strlen(channel)*2+1);
615         while (*channel != '\0') {
616                 if ((unsigned char) *channel >= 32)
617                         *p++ = *channel;
618                 else {
619                         *p++ = '^';
620                         *p++ = *channel + 'A'-1;
621                 }
622                 channel++;
623         }
624         *p = '\0';
625
626         return str;
627 }
628
629 /* Get time in human readable form with localtime() + asctime() */
630 char *my_asctime(time_t t)
631 {
632         struct tm *tm;
633         char *str;
634         int len;
635
636         tm = localtime(&t);
637         str = g_strdup(asctime(tm));
638
639         len = strlen(str);
640         if (len > 0) str[len-1] = '\0';
641         return str;
642 }
643
644 /* Returns number of columns needed to print items.
645    save_column_widths is filled with length of each column. */
646 int get_max_column_count(GSList *items, COLUMN_LEN_FUNC len_func,
647                          int max_width, int max_columns,
648                          int item_extra, int item_min_size,
649                          int **save_column_widths, int *rows)
650 {
651         GSList *tmp;
652         int **columns, *columns_width, *columns_rows;
653         int item_pos, items_count;
654         int ret, len, max_len, n, col;
655
656         items_count = g_slist_length(items);
657         if (items_count == 0) {
658                 *save_column_widths = NULL;
659                 *rows = 0;
660                 return 0;
661         }
662
663         len = max_width/(item_extra+item_min_size);
664         if (len <= 0) len = 1;
665         if (max_columns <= 0 || len < max_columns)
666                 max_columns = len;
667
668         columns = g_new0(int *, max_columns);
669         columns_width = g_new0(int, max_columns);
670         columns_rows = g_new0(int, max_columns);
671
672         for (n = 1; n < max_columns; n++) {
673                 columns[n] = g_new0(int, n+1);
674                 columns_rows[n] = items_count <= n+1 ? 1 :
675                         (items_count+n)/(n+1);
676         }
677
678         /* for each possible column count, save the column widths and
679            find the biggest column count that fits to screen. */
680         item_pos = 0; max_len = 0;
681         for (tmp = items; tmp != NULL; tmp = tmp->next) {
682                 len = item_extra+len_func(tmp->data);
683                 if (max_len < len)
684                         max_len = len;
685
686                 for (n = 1; n < max_columns; n++) {
687                         if (columns_width[n] > max_width)
688                                 continue; /* too wide */
689
690                         col = item_pos/columns_rows[n];
691                         if (columns[n][col] < len) {
692                                 columns_width[n] += len-columns[n][col];
693                                 columns[n][col] = len;
694                         }
695                 }
696
697                 item_pos++;
698         }
699
700         for (n = max_columns-1; n >= 1; n--) {
701                 if (columns_width[n] <= max_width &&
702                     columns[n][n] > 0)
703                         break;
704         }
705         ret = n+1;
706
707         *save_column_widths = g_new(int, ret);
708         if (ret == 1) {
709                 **save_column_widths = max_len;
710                 *rows = 1;
711         } else {
712                 memcpy(*save_column_widths, columns[ret-1], sizeof(int)*ret);
713                 *rows = columns_rows[ret-1];
714         }
715
716         for (n = 1; n < max_columns; n++)
717                 g_free(columns[n]);
718         g_free(columns_width);
719         g_free(columns_rows);
720         g_free(columns);
721
722         return ret;
723 }
724
725 /* Return a column sorted copy of a list. */
726 GSList *columns_sort_list(GSList *list, int rows)
727 {
728         GSList *tmp, *sorted;
729         int row, skip;
730
731         if (list == NULL || rows == 0)
732                 return list;
733
734         sorted = NULL;
735
736         for (row = 0; row < rows; row++) {
737                 tmp = g_slist_nth(list, row);
738                 skip = 1;
739                 for (; tmp != NULL; tmp = tmp->next) {
740                         if (--skip == 0) {
741                                 skip = rows;
742                                 sorted = g_slist_append(sorted, tmp->data);
743                         }
744                 }
745         }
746
747         g_return_val_if_fail(g_slist_length(sorted) ==
748                              g_slist_length(list), sorted);
749         return sorted;
750 }
751
752 /* Expand escape string, the first character in data should be the
753    one after '\'. Returns the expanded character or -1 if error. */
754 int expand_escape(const char **data)
755 {
756         char digit[4];
757
758         switch (**data) {
759         case 't':
760                 return '\t';
761         case 'r':
762                 return '\r';
763         case 'n':
764                 return '\n';
765         case 'e':
766                 return 27; /* ESC */
767
768         case 'x':
769                 /* hex digit */
770                 if (!i_isxdigit((*data)[1]) || !i_isxdigit((*data)[2]))
771                         return -1;
772
773                 digit[0] = (*data)[1];
774                 digit[1] = (*data)[2];
775                 digit[2] = '\0';
776                 *data += 2;
777                 return strtol(digit, NULL, 16);
778         case 'c':
779                 /* control character (\cA = ^A) */
780                 (*data)++;
781                 return i_toupper(**data) - 64;
782         default:
783                 if (!i_isdigit(**data))
784                         return -1;
785
786                 /* octal */
787                 digit[0] = (*data)[0];
788                 digit[1] = (*data)[1];
789                 digit[2] = (*data)[2];
790                 digit[3] = '\0';
791                 *data += 2;
792                 return strtol(digit, NULL, 8);
793         }
794 }
795
796 /* Escape all '"', "'" and '\' chars with '\' */
797 char *escape_string(const char *str)
798 {
799         char *ret, *p;
800
801         p = ret = g_malloc(strlen(str)*2+1);
802         while (*str != '\0') {
803                 if (*str == '"' || *str == '\'' || *str == '\\')
804                         *p++ = '\\';
805                 *p++ = *str++;
806         }
807         *p = '\0';
808
809         return ret;
810 }