source: util.c @ d275eb2

release-1.10release-1.8release-1.9
Last change on this file since d275eb2 was d275eb2, checked in by David Benjamin <davidben@mit.edu>, 14 years ago
Replace atokenize with glib's g_strsplit_set Glib's had g_strsplit_set since forever, and it does exactly the same thing as atokenize, modulo needing a pesky NULL check. We may as well use it.
  • Property mode set to 100644
File size: 18.1 KB
Line 
1#include "owl.h"
2#include <stdlib.h>
3#include <string.h>
4#include <unistd.h>
5#include <ctype.h>
6#include <pwd.h>
7#include <sys/stat.h>
8#include <sys/types.h>
9#include <assert.h>
10#include <glib.h>
11#include <glib/gstdio.h>
12#include <glib-object.h>
13
14const char *skiptokens(const char *buff, int n) {
15  /* skips n tokens and returns where that would be. */
16  char quote = 0;
17  while (*buff && n>0) {
18      while (*buff == ' ') buff++;
19      while (*buff && (quote || *buff != ' ')) {
20        if(quote) {
21          if(*buff == quote) quote = 0;
22        } else if(*buff == '"' || *buff == '\'') {
23          quote = *buff;
24        }
25        buff++;
26      }
27      while (*buff == ' ') buff++;
28      n--;
29  }
30  return buff;
31}
32
33/* Return a "nice" version of the path.  Tilde expansion is done, and
34 * duplicate slashes are removed.  Caller must free the return.
35 */
36char *owl_util_makepath(const char *in)
37{
38  int i, j, x;
39  char *out, user[MAXPATHLEN];
40  struct passwd *pw;
41
42  out=owl_malloc(MAXPATHLEN+1);
43  out[0]='\0';
44  j=strlen(in);
45  x=0;
46  for (i=0; i<j; i++) {
47    if (in[i]=='~') {
48      if ( (i==(j-1)) ||          /* last character */
49           (in[i+1]=='/') ) {     /* ~/ */
50        /* use my homedir */
51        pw=getpwuid(getuid());
52        if (!pw) {
53          out[x]=in[i];
54        } else {
55          out[x]='\0';
56          strcat(out, pw->pw_dir);
57          x+=strlen(pw->pw_dir);
58        }
59      } else {
60        /* another user homedir */
61        int a, b;
62        b=0;
63        for (a=i+1; i<j; a++) {
64          if (in[a]==' ' || in[a]=='/') {
65            break;
66          } else {
67            user[b]=in[a];
68            i++;
69            b++;
70          }
71        }
72        user[b]='\0';
73        pw=getpwnam(user);
74        if (!pw) {
75          out[x]=in[i];
76        } else {
77          out[x]='\0';
78          strcat(out, pw->pw_dir);
79          x+=strlen(pw->pw_dir);
80        }
81      }
82    } else if (in[i]=='/') {
83      /* check for a double / */
84      if (i<(j-1) && (in[i+1]=='/')) {
85        /* do nothing */
86      } else {
87        out[x]=in[i];
88        x++;
89      }
90    } else {
91      out[x]=in[i];
92      x++;
93    }
94  }
95  out[x]='\0';
96  return(out);
97}
98
99void owl_parse_delete(char **argv, int argc)
100{
101  int i;
102
103  if (!argv) return;
104 
105  for (i=0; i<argc; i++) {
106    if (argv[i]) owl_free(argv[i]);
107  }
108  owl_free(argv);
109}
110
111char **owl_parseline(const char *line, int *argc)
112{
113  /* break a command line up into argv, argc.  The caller must free
114     the returned values.  If there is an error argc will be set to
115     -1, argv will be NULL and the caller does not need to free
116     anything */
117
118  char **argv;
119  int i, len, between=1;
120  char *curarg;
121  char quote;
122
123  argv=owl_malloc(sizeof(char *));
124  len=strlen(line);
125  curarg=owl_malloc(len+10);
126  strcpy(curarg, "");
127  quote='\0';
128  *argc=0;
129  for (i=0; i<len+1; i++) {
130    /* find the first real character */
131    if (between) {
132      if (line[i]==' ' || line[i]=='\t' || line[i]=='\0') {
133        continue;
134      } else {
135        between=0;
136        i--;
137        continue;
138      }
139    }
140
141    /* deal with a quote character */
142    if (line[i]=='"' || line[i]=="'"[0]) {
143      /* if this type of quote is open, close it */
144      if (quote==line[i]) {
145        quote='\0';
146        continue;
147      }
148
149      /* if no quoting is open then open with this */
150      if (quote=='\0') {
151        quote=line[i];
152        continue;
153      }
154
155      /* if another type of quote is open then treat this as a literal */
156      curarg[strlen(curarg)+1]='\0';
157      curarg[strlen(curarg)]=line[i];
158      continue;
159    }
160
161    /* if it's not a space or end of command, then use it */
162    if (line[i]!=' ' && line[i]!='\t' && line[i]!='\n' && line[i]!='\0') {
163      curarg[strlen(curarg)+1]='\0';
164      curarg[strlen(curarg)]=line[i];
165      continue;
166    }
167
168    /* otherwise, if we're not in quotes, add the whole argument */
169    if (quote=='\0') {
170      /* add the argument */
171      argv=owl_realloc(argv, sizeof(char *)*((*argc)+1));
172      argv[*argc] = owl_strdup(curarg);
173      *argc=*argc+1;
174      strcpy(curarg, "");
175      between=1;
176      continue;
177    }
178
179    /* if it is a space and we're in quotes, then use it */
180    curarg[strlen(curarg)+1]='\0';
181    curarg[strlen(curarg)]=line[i];
182  }
183
184  owl_free(curarg);
185
186  /* check for unbalanced quotes */
187  if (quote!='\0') {
188    owl_parse_delete(argv, *argc);
189    *argc=-1;
190    return(NULL);
191  }
192
193  return(argv);
194}
195
196/* caller must free the return */
197char *owl_util_minutes_to_timestr(int in)
198{
199  int days, hours;
200  long run;
201  char *out;
202
203  run=in;
204
205  days=run/1440;
206  run-=days*1440;
207  hours=run/60;
208  run-=hours*60;
209
210  if (days>0) {
211    out=owl_sprintf("%i d %2.2i:%2.2li", days, hours, run);
212  } else {
213    out=owl_sprintf("    %2.2i:%2.2li", hours, run);
214  }
215  return(out);
216}
217
218/* hooks for doing memory allocation et. al. in owl */
219
220void *owl_malloc(size_t size)
221{
222  return(g_malloc(size));
223}
224
225void owl_free(void *ptr)
226{
227  g_free(ptr);
228}
229
230char *owl_strdup(const char *s1)
231{
232  return(g_strdup(s1));
233}
234
235void *owl_realloc(void *ptr, size_t size)
236{
237  return(g_realloc(ptr, size));
238}
239
240/* allocates memory and returns the string or null.
241 * caller must free the string.
242 */
243char *owl_sprintf(const char *fmt, ...)
244{
245  va_list ap;
246  char *ret = NULL;
247  va_start(ap, fmt);
248  ret = g_strdup_vprintf(fmt, ap);
249  va_end(ap);
250  return ret;
251}
252
253/* These are in order of their value in owl.h */
254static const struct {
255  int number;
256  const char *name;
257} color_map[] = {
258  {OWL_COLOR_INVALID, "invalid"},
259  {OWL_COLOR_DEFAULT, "default"},
260  {OWL_COLOR_BLACK, "black"},
261  {OWL_COLOR_RED, "red"},
262  {OWL_COLOR_GREEN, "green"},
263  {OWL_COLOR_YELLOW,"yellow"},
264  {OWL_COLOR_BLUE, "blue"},
265  {OWL_COLOR_MAGENTA, "magenta"},
266  {OWL_COLOR_CYAN, "cyan"},
267  {OWL_COLOR_WHITE, "white"},
268};
269
270/* Return the owl color associated with the named color.  Return -1
271 * if the named color is not available
272 */
273int owl_util_string_to_color(const char *color)
274{
275  int c, i;
276  char *p;
277
278  for (i = 0; i < (sizeof(color_map)/sizeof(color_map[0])); i++)
279    if (strcasecmp(color, color_map[i].name) == 0)
280      return color_map[i].number;
281
282  c = strtol(color, &p, 10);
283  if (p != color && c >= -1 && c < COLORS) {
284    return(c);
285  }
286  return(OWL_COLOR_INVALID);
287}
288
289/* Return a string name of the given owl color */
290const char *owl_util_color_to_string(int color)
291{
292  if (color >= OWL_COLOR_INVALID && color <= OWL_COLOR_WHITE)
293    return color_map[color - OWL_COLOR_INVALID].name;
294  return("Unknown color");
295}
296
297/* Get the default tty name.  Caller must free the return */
298char *owl_util_get_default_tty(void)
299{
300  const char *tmp;
301  char *out;
302
303  if (getenv("DISPLAY")) {
304    out=owl_strdup(getenv("DISPLAY"));
305  } else if ((tmp=ttyname(fileno(stdout)))!=NULL) {
306    out=owl_strdup(tmp);
307    if (!strncmp(out, "/dev/", 5)) {
308      owl_free(out);
309      out=owl_strdup(tmp+5);
310    }
311  } else {
312    out=owl_strdup("unknown");
313  }
314  return(out);
315}
316
317/* strip leading and trailing new lines.  Caller must free the
318 * return.
319 */
320char *owl_util_stripnewlines(const char *in)
321{
322 
323  char  *tmp, *ptr1, *ptr2, *out;
324
325  ptr1=tmp=owl_strdup(in);
326  while (ptr1[0]=='\n') {
327    ptr1++;
328  }
329  ptr2=ptr1+strlen(ptr1)-1;
330  while (ptr2>ptr1 && ptr2[0]=='\n') {
331    ptr2[0]='\0';
332    ptr2--;
333  }
334
335  out=owl_strdup(ptr1);
336  owl_free(tmp);
337  return(out);
338}
339
340
341/* If filename is a link, recursively resolve symlinks.  Otherwise, return the filename
342 * unchanged.  On error, call owl_function_error and return NULL.
343 *
344 * This function assumes that filename eventually resolves to an acutal file.
345 * If you want to check this, you should stat() the file first.
346 *
347 * The caller of this function is responsible for freeing the return value.
348 *
349 * Error conditions are the same as g_file_read_link.
350 */
351gchar *owl_util_recursive_resolve_link(const char *filename)
352{
353  gchar *last_path = g_strdup(filename);
354  GError *err = NULL;
355
356  while (g_file_test(last_path, G_FILE_TEST_IS_SYMLINK)) {
357    gchar *link_path = g_file_read_link(last_path, &err);
358    if (link_path == NULL) {
359      owl_function_error("Cannot resolve symlink %s: %s",
360                         last_path, err->message);
361      g_error_free(err);
362      g_free(last_path);
363      return NULL;
364    }
365
366    /* Deal with obnoxious relative paths. If we really care, all this
367     * is racy. Whatever. */
368    if (!g_path_is_absolute(link_path)) {
369      char *last_dir = g_path_get_dirname(last_path);
370      char *tmp = g_build_path(G_DIR_SEPARATOR_S,
371                               last_dir,
372                               link_path,
373                               NULL);
374      g_free(last_dir);
375      g_free(link_path);
376      link_path = tmp;
377    }
378
379    g_free(last_path);
380    last_path = link_path;
381  }
382  return last_path;
383}
384
385/* Delete all lines matching "line" from the named file.  If no such
386 * line is found the file is left intact.  If backup==1 then leave a
387 * backup file containing the original contents.  The match is
388 * case-insensitive.
389 *
390 * Returns the number of lines removed on success.  Returns -1 on failure.
391 */
392int owl_util_file_deleteline(const char *filename, const char *line, int backup)
393{
394  char *backupfile, *newfile, *buf = NULL;
395  gchar *actual_filename; /* gchar; we need to g_free it */
396  FILE *old, *new;
397  struct stat st;
398  int numremoved = 0;
399
400  if ((old = fopen(filename, "r")) == NULL) {
401    owl_function_error("Cannot open %s (for reading): %s",
402                       filename, strerror(errno));
403    return -1;
404  }
405
406  if (fstat(fileno(old), &st) != 0) {
407    owl_function_error("Cannot stat %s: %s", filename, strerror(errno));
408    return -1;
409  }
410
411  /* resolve symlinks, because link() fails on symlinks, at least on AFS */
412  actual_filename = owl_util_recursive_resolve_link(filename);
413  if (actual_filename == NULL)
414    return -1; /* resolving the symlink failed, but we already logged this error */
415
416  newfile = owl_sprintf("%s.new", actual_filename);
417  if ((new = fopen(newfile, "w")) == NULL) {
418    owl_function_error("Cannot open %s (for writing): %s",
419                       actual_filename, strerror(errno));
420    owl_free(newfile);
421    fclose(old);
422    free(actual_filename);
423    return -1;
424  }
425
426  if (fchmod(fileno(new), st.st_mode & 0777) != 0) {
427    owl_function_error("Cannot set permissions on %s: %s",
428                       actual_filename, strerror(errno));
429    unlink(newfile);
430    fclose(new);
431    owl_free(newfile);
432    fclose(old);
433    free(actual_filename);
434    return -1;
435  }
436
437  while (owl_getline_chomp(&buf, old))
438    if (strcasecmp(buf, line) != 0)
439      fprintf(new, "%s\n", buf);
440    else
441      numremoved++;
442  owl_free(buf);
443
444  fclose(new);
445  fclose(old);
446
447  if (backup) {
448    backupfile = owl_sprintf("%s.backup", actual_filename);
449    unlink(backupfile);
450    if (link(actual_filename, backupfile) != 0) {
451      owl_function_error("Cannot link %s: %s", backupfile, strerror(errno));
452      owl_free(backupfile);
453      unlink(newfile);
454      owl_free(newfile);
455      return -1;
456    }
457    owl_free(backupfile);
458  }
459
460  if (rename(newfile, actual_filename) != 0) {
461    owl_function_error("Cannot move %s to %s: %s",
462                       newfile, actual_filename, strerror(errno));
463    numremoved = -1;
464  }
465
466  unlink(newfile);
467  owl_free(newfile);
468
469  g_free(actual_filename);
470
471  return numremoved;
472}
473
474/* Return the base class or instance from a zephyr class, by removing
475   leading `un' or trailing `.d'.
476   The caller is responsible for freeing the allocated string.
477*/
478char * owl_util_baseclass(const char * class)
479{
480  char *start, *end;
481
482  while(!strncmp(class, "un", 2)) {
483    class += 2;
484  }
485
486  start = owl_strdup(class);
487  end = start + strlen(start) - 1;
488  while(end > start && *end == 'd' && *(end-1) == '.') {
489    end -= 2;
490  }
491  *(end + 1) = 0;
492
493  return start;
494}
495
496const char * owl_get_datadir(void)
497{
498  const char * datadir = getenv("BARNOWL_DATA_DIR");
499  if(datadir != NULL)
500    return datadir;
501  return DATADIR;
502}
503
504const char * owl_get_bindir(void)
505{
506  const char * bindir = getenv("BARNOWL_BIN_DIR");
507  if(bindir != NULL)
508    return bindir;
509  return BINDIR;
510}
511
512/* Strips format characters from a valid utf-8 string. Returns the
513   empty string if 'in' does not validate. */
514char * owl_strip_format_chars(const char *in)
515{
516  char *r;
517  if (g_utf8_validate(in, -1, NULL)) {
518    const char *s, *p;
519    r = owl_malloc(strlen(in)+1);
520    r[0] = '\0';
521    s = in;
522    p = strchr(s, OWL_FMTEXT_UC_STARTBYTE_UTF8);
523    while(p) {
524      /* If it's a format character, copy up to it, and skip all
525         immediately following format characters. */
526      if (owl_fmtext_is_format_char(g_utf8_get_char(p))) {
527        strncat(r, s, p-s);
528        p = g_utf8_next_char(p);
529        while (owl_fmtext_is_format_char(g_utf8_get_char(p))) {
530          p = g_utf8_next_char(p);
531        }
532        s = p;
533        p = strchr(s, OWL_FMTEXT_UC_STARTBYTE_UTF8);
534      }
535      else {
536        p = strchr(p+1, OWL_FMTEXT_UC_STARTBYTE_UTF8);
537      }
538    }
539    if (s) strcat(r,s);
540  }
541  else {
542    r = owl_strdup("");
543  }
544  return r;
545}
546
547/* If in is not UTF-8, convert from ISO-8859-1. We may want to allow
548 * the caller to specify an alternative in the future. We also strip
549 * out characters in Unicode Plane 16, as we use that plane internally
550 * for formatting.
551 */
552char * owl_validate_or_convert(const char *in)
553{
554  if (g_utf8_validate(in, -1, NULL)) {
555    return owl_strip_format_chars(in);
556  }
557  else {
558    return g_convert(in, -1,
559                     "UTF-8", "ISO-8859-1",
560                     NULL, NULL, NULL);
561  }
562}
563/*
564 * Validate 'in' as UTF-8, and either return a copy of it, or an empty
565 * string if it is invalid utf-8.
566 */
567char * owl_validate_utf8(const char *in)
568{
569  char *out;
570  if (g_utf8_validate(in, -1, NULL)) {
571    out = owl_strdup(in);
572  } else {
573    out = owl_strdup("");
574  }
575  return out;
576}
577
578/* This is based on _extract() and _isCJ() from perl's Text::WrapI18N */
579int owl_util_can_break_after(gunichar c)
580{
581 
582  if (c == ' ') return 1;
583  if (c >= 0x3000 && c <= 0x312f) {
584    /* CJK punctuations, Hiragana, Katakana, Bopomofo */
585    if (c == 0x300a || c == 0x300c || c == 0x300e ||
586        c == 0x3010 || c == 0x3014 || c == 0x3016 ||
587        c == 0x3018 || c == 0x301a)
588      return 0;
589    return 1;
590  }
591  if (c >= 0x31a0 && c <= 0x31bf) {return 1;}  /* Bopomofo */
592  if (c >= 0x31f0 && c <= 0x31ff) {return 1;}  /* Katakana extension */
593  if (c >= 0x3400 && c <= 0x9fff) {return 1;}  /* Han Ideogram */
594  if (c >= 0xf900 && c <= 0xfaff) {return 1;}  /* Han Ideogram */
595  if (c >= 0x20000 && c <= 0x2ffff) {return 1;}  /* Han Ideogram */
596  return 0;
597}
598
599char *owl_escape_highbit(const char *str)
600{
601  GString *out = g_string_new("");
602  unsigned char c;
603  while((c = (*str++))) {
604    if(c == '\\') {
605      g_string_append(out, "\\\\");
606    } else if(c & 0x80) {
607      g_string_append_printf(out, "\\x%02x", (int)c);
608    } else {
609      g_string_append_c(out, c);
610    }
611  }
612  return g_string_free(out, 0);
613}
614
615/* innards of owl_getline{,_chomp} below */
616static int owl_getline_internal(char **s, FILE *fp, int newline)
617{
618  int size = 0;
619  int target = 0;
620  int count = 0;
621  int c;
622
623  while (1) {
624    c = getc(fp);
625    if ((target + 1) > size) {
626      size += BUFSIZ;
627      *s = owl_realloc(*s, size);
628    }
629    if (c == EOF)
630      break;
631    count++;
632    if (c != '\n' || newline)
633        (*s)[target++] = c;
634    if (c == '\n')
635      break;
636  }
637  (*s)[target] = 0;
638
639  return count;
640}
641
642/* Read a line from fp, allocating memory to hold it, returning the number of
643 * byte read.  *s should either be NULL or a pointer to memory allocated with
644 * owl_malloc; it will be owl_realloc'd as appropriate.  The caller must
645 * eventually free it.  (This is roughly the interface of getline in the gnu
646 * libc).
647 *
648 * The final newline will be included if it's there.
649 */
650int owl_getline(char **s, FILE *fp)
651{
652  return owl_getline_internal(s, fp, 1);
653}
654
655/* As above, but omitting the final newline */
656int owl_getline_chomp(char **s, FILE *fp)
657{
658  return owl_getline_internal(s, fp, 0);
659}
660
661/* Read the rest of the input available in fp into a string. */
662char *owl_slurp(FILE *fp)
663{
664  char *buf = NULL;
665  char *p;
666  int size = 0;
667  int count;
668
669  while (1) {
670    buf = owl_realloc(buf, size + BUFSIZ);
671    p = &buf[size];
672    size += BUFSIZ;
673
674    if ((count = fread(p, 1, BUFSIZ, fp)) < BUFSIZ)
675      break;
676  }
677  p[count] = 0;
678
679  return buf;
680}
681
682gulong owl_dirty_window_on_signal(owl_window *w, gpointer sender, const gchar *detailed_signal)
683{
684  return owl_signal_connect_object(sender, detailed_signal, G_CALLBACK(owl_window_dirty), w, G_CONNECT_SWAPPED);
685}
686
687typedef struct { /*noproto*/
688  GObject  *sender;
689  gulong    signal_id;
690} SignalData;
691
692static void _closure_invalidated(gpointer data, GClosure *closure);
693
694/*
695 * GObject's g_signal_connect_object has a documented bug. This function is
696 * identical except it does not leak the signal handler.
697 */
698gulong owl_signal_connect_object(gpointer sender, const gchar *detailed_signal, GCallback c_handler, gpointer receiver, GConnectFlags connect_flags)
699{
700  g_return_val_if_fail (G_TYPE_CHECK_INSTANCE (sender), 0);
701  g_return_val_if_fail (detailed_signal != NULL, 0);
702  g_return_val_if_fail (c_handler != NULL, 0);
703
704  if (receiver) {
705    SignalData *sdata;
706    GClosure *closure;
707    gulong signal_id;
708
709    g_return_val_if_fail (G_IS_OBJECT (receiver), 0);
710
711    closure = ((connect_flags & G_CONNECT_SWAPPED) ? g_cclosure_new_object_swap : g_cclosure_new_object) (c_handler, receiver);
712    signal_id = g_signal_connect_closure (sender, detailed_signal, closure, connect_flags & G_CONNECT_AFTER);
713
714    /* Register the missing hooks */
715    sdata = g_slice_new0(SignalData);
716    sdata->sender = sender;
717    sdata->signal_id = signal_id;
718
719    g_closure_add_invalidate_notifier(closure, sdata, _closure_invalidated);
720
721    return signal_id;
722  } else {
723    return g_signal_connect_data(sender, detailed_signal, c_handler, NULL, NULL, connect_flags);
724  }
725}
726
727/*
728 * There are three ways the signal could come to an end:
729 *
730 * 1. The user explicitly disconnects it with the returned signal_id.
731 *    - In that case, the disconnection unref's the closure, causing it
732 *      to first be invalidated. The handler's already disconnected, so
733 *      we have no work to do.
734 * 2. The sender gets destroyed.
735 *    - GObject will disconnect each signal which then goes into the above
736 *      case. Our handler does no work.
737 * 3. The receiver gets destroyed.
738 *    - The GClosure was created by g_cclosure_new_object_{,swap} which gets
739 *      invalidated when the receiver is destroyed. We then follow through case 1
740 *      again, but *this* time, the handler has not been disconnected. We then
741 *      clean up ourselves.
742 *
743 * We can't actually hook into this process earlier with weakrefs as GObject
744 * will, on object dispose, first disconnect signals, then invalidate closures,
745 * and notify weakrefs last.
746 */
747static void _closure_invalidated(gpointer data, GClosure *closure)
748{
749  SignalData *sdata = data;
750  if (g_signal_handler_is_connected(sdata->sender, sdata->signal_id)) {
751    g_signal_handler_disconnect(sdata->sender, sdata->signal_id);
752  }
753  g_slice_free(SignalData, sdata);
754}
755
Note: See TracBrowser for help on using the repository browser.