source: util.c @ cb1759e

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