mail-notification

Fork of Jean-Yves Lefort's mail-notification, a tray icon to notify of new mail
git clone https://code.djc.id.au/git/mail-notification/

jbsrc/lib/src/core/jb-util.c (20674B) - raw

      1 /*
      2  * JB, the Jean-Yves Lefort's Build System
      3  * Copyright (C) 2008 Jean-Yves Lefort <jylefort@brutele.be>
      4  *
      5  * This program is free software; you can redistribute it and/or modify
      6  * it under the terms of the GNU General Public License as published by
      7  * the Free Software Foundation; either version 3 of the License, or
      8  * (at your option) any later version.
      9  *
     10  * This program is distributed in the hope that it will be useful,
     11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  * GNU General Public License for more details.
     14  *
     15  * You should have received a copy of the GNU General Public License along
     16  * with this program; if not, write to the Free Software Foundation, Inc.,
     17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
     18  */
     19 
     20 #include <stdio.h>
     21 #include <string.h>
     22 #include <stdlib.h>
     23 #include <unistd.h>
     24 #include <sys/types.h>
     25 #include <sys/stat.h>
     26 #include <sys/wait.h>
     27 #include <pwd.h>
     28 #include <grp.h>
     29 #include <errno.h>
     30 #include <glob.h>
     31 #include "jb-util.h"
     32 #include "jb-variable.h"
     33 #include "jb-main.h"
     34 
     35 static char *log_file = NULL;
     36 
     37 static gboolean printing_action = FALSE;
     38 
     39 void
     40 jb_set_log_file (const char *filename)
     41 {
     42   g_return_if_fail(filename != NULL);
     43 
     44   g_free(log_file);
     45   log_file = g_strdup(filename);
     46 }
     47 
     48 void
     49 jb_log (const char *format, ...)
     50 {
     51   static gboolean logging = FALSE;
     52   static GIOChannel *log_channel = NULL;
     53   static char *current_log_file = NULL;
     54   char *message;
     55   char *with_nl;
     56   GError *err = NULL;
     57   gsize bytes_written;
     58 
     59   g_return_if_fail(format != NULL);
     60   g_return_if_fail(log_file != NULL);
     61 
     62   if (logging)
     63     return;
     64 
     65   logging = TRUE;
     66 
     67   if (log_channel != NULL)
     68     {
     69       if (strcmp(current_log_file, log_file))
     70 	{
     71 	  if (g_io_channel_shutdown(log_channel, TRUE, &err) != G_IO_STATUS_NORMAL)
     72 	    jb_error("unable to write to %s: %s", current_log_file, err->message);
     73 
     74 	  g_io_channel_unref(log_channel);
     75 	  log_channel = NULL;
     76 
     77 	  g_free(current_log_file);
     78 	}
     79     }
     80 
     81   if (log_channel == NULL)
     82     {
     83       current_log_file = g_strdup(log_file);
     84 
     85       log_channel = g_io_channel_new_file(log_file, "w", &err);
     86       if (log_channel == NULL)
     87 	jb_error("unable to open %s for writing: %s", log_file, err->message);
     88     }
     89 
     90   JB_STRDUP_VPRINTF(message, format);
     91 
     92   with_nl = g_strdup_printf("%s\n", message);
     93   g_free(message);
     94 
     95   if (g_io_channel_write_chars(log_channel, with_nl, -1, &bytes_written, &err) != G_IO_STATUS_NORMAL
     96       || g_io_channel_flush(log_channel, &err) != G_IO_STATUS_NORMAL)
     97     jb_error("unable to write to %s: %s", log_file, err->message);
     98 
     99   g_free(with_nl);
    100 
    101   logging = FALSE;
    102 }
    103 
    104 void
    105 jb_message (const char *format, ...)
    106 {
    107   char *message;
    108 
    109   g_return_if_fail(format != NULL);
    110   g_return_if_fail(! printing_action);
    111 
    112   JB_STRDUP_VPRINTF(message, format);
    113 
    114   g_print("%s\n", message);
    115   jb_log("%s", message);
    116 
    117   g_free(message);
    118 }
    119 
    120 void
    121 jb_message_expand (const char *str, ...)
    122 {
    123   va_list args;
    124   char *message;
    125 
    126   g_return_if_fail(str != NULL);
    127 
    128   va_start(args, str);
    129   message = jb_variable_expandv(str, args);
    130   va_end(args);
    131 
    132   jb_message("%s", message);
    133   g_free(message);
    134 }
    135 
    136 void
    137 jb_message_action (const char *format, ...)
    138 {
    139   char *message;
    140 
    141   g_return_if_fail(format != NULL);
    142   g_return_if_fail(! printing_action);
    143 
    144   JB_STRDUP_VPRINTF(message, format);
    145 
    146   g_print("%s...", message);
    147   jb_log("%s", message);
    148 
    149   g_free(message);
    150 
    151   printing_action = TRUE;
    152 }
    153 
    154 void
    155 jb_message_checking (const char *format, ...)
    156 {
    157   char *message;
    158 
    159   g_return_if_fail(format != NULL);
    160   g_return_if_fail(! printing_action);
    161 
    162   JB_STRDUP_VPRINTF(message, format);
    163   jb_message_action("checking %s", message);
    164   g_free(message);
    165 }
    166 
    167 void
    168 jb_message_result_bool (gboolean result)
    169 {
    170   g_return_if_fail(printing_action);
    171 
    172   jb_message_result_string(result ? "yes" : "no");
    173 }
    174 
    175 void
    176 jb_message_result_string (const char *result)
    177 {
    178   g_return_if_fail(result != NULL);
    179   g_return_if_fail(printing_action);
    180 
    181   g_print(" %s\n", result);
    182   jb_log("result: %s", result);
    183   jb_log("");
    184 
    185   printing_action = FALSE;
    186 }
    187 
    188 static void
    189 finish_printing_action (void)
    190 {
    191   if (printing_action)
    192     {
    193       g_print("\n");
    194       printing_action = FALSE;
    195     }
    196 }
    197 
    198 void
    199 jb_message_result_string_format (const char *format, ...)
    200 {
    201   char *message;
    202 
    203   g_return_if_fail(format != NULL);
    204   g_return_if_fail(printing_action);
    205 
    206   JB_STRDUP_VPRINTF(message, format);
    207   jb_message_result_string(message);
    208   g_free(message);
    209 }
    210 
    211 static void
    212 print_warning_or_error (const char *prefix, const char *format, va_list args)
    213 {
    214   char *message;
    215   char **lines;
    216   int i;
    217 
    218   /*
    219    * We allow to interrupt an action print, in case the caller is a
    220    * library function which does not know that an action is in
    221    * progress.
    222    */
    223   finish_printing_action();
    224 
    225   message = g_strdup_vprintf(format, args);
    226   lines = g_strsplit(message, "\n", 0);
    227   g_free(message);
    228 
    229   for (i = 0; lines[i] != NULL; i++)
    230     {
    231       const char *line = lines[i];
    232 
    233       g_printerr("%s: %s\n", prefix, line);
    234       jb_log("%s: %s", prefix, line);
    235     }
    236 
    237   g_strfreev(lines);
    238 }
    239 
    240 void
    241 jb_warning (const char *format, ...)
    242 {
    243   va_list args;
    244 
    245   g_return_if_fail(format != NULL);
    246 
    247   va_start(args, format);
    248   print_warning_or_error("WARNING", format, args);
    249   va_end(args);
    250 }
    251 
    252 void
    253 jb_warning_expand (const char *str, ...)
    254 {
    255   va_list args;
    256   char *message;
    257 
    258   g_return_if_fail(str != NULL);
    259 
    260   va_start(args, str);
    261   message = jb_variable_expandv(str, args);
    262   va_end(args);
    263 
    264   jb_warning("%s", message);
    265   g_free(message);
    266 }
    267 
    268 void
    269 jb_error (const char *format, ...)
    270 {
    271   va_list args;
    272 
    273   g_assert(format != NULL);
    274 
    275   va_start(args, format);
    276   print_warning_or_error("ERROR", format, args);
    277   va_end(args);
    278 
    279   exit(1);
    280 }
    281 
    282 void
    283 jb_error_expand (const char *str, ...)
    284 {
    285   va_list args;
    286   char *message;
    287 
    288   g_assert(str != NULL);
    289 
    290   va_start(args, str);
    291   message = jb_variable_expandv(str, args);
    292   va_end(args);
    293 
    294   jb_error("%s", message);
    295   g_free(message);
    296 }
    297 
    298 void
    299 jb_g_slist_free_deep (GSList *list)
    300 {
    301   jb_g_slist_free_deep_custom(list, (GFunc) g_free, NULL);
    302 }
    303 
    304 void
    305 jb_g_slist_free_deep_custom (GSList *list,
    306 			     GFunc element_free_func,
    307 			     gpointer user_data)
    308 {
    309   g_slist_foreach(list, element_free_func, user_data);
    310   g_slist_free(list);
    311 }
    312 
    313 char *
    314 jb_strdelimit (const char *str, const char *delimiters, char new_delimiter)
    315 {
    316   char *result;
    317 
    318   g_return_val_if_fail(str != NULL, NULL);
    319   g_return_val_if_fail(delimiters != NULL, NULL);
    320 
    321   result = g_strdup(str);
    322   g_strdelimit(result, delimiters, new_delimiter);
    323 
    324   return result;
    325 }
    326 
    327 char *
    328 jb_strip_newline (const char *str)
    329 {
    330   int len;
    331 
    332   g_return_val_if_fail(str != NULL, NULL);
    333 
    334   len = strlen(str);
    335   if (len > 0 && str[len - 1] == '\n')
    336     return g_strndup(str, len - 1);
    337   else
    338     return g_strdup(str);
    339 }
    340 
    341 char *
    342 jb_c_quote (const char *str)
    343 {
    344   GString *result;
    345   const char *p;
    346 
    347   g_return_val_if_fail(str != NULL, NULL);
    348 
    349   result = g_string_new("\"");
    350 
    351   for (p = str; *p != '\0'; p++)
    352     {
    353       char c = *p;
    354 
    355       switch (c)
    356 	{
    357 	case '\\':
    358 	  g_string_append(result, "\\\\");
    359 	  break;
    360 
    361 	case '"':
    362 	  g_string_append(result, "\\\"");
    363 	  break;
    364 
    365 	case '\r':
    366 	  g_string_append(result, "\\r");
    367 	  break;
    368 
    369 	case '\n':
    370 	  g_string_append(result, "\\n");
    371 	  break;
    372 
    373 	case '\t':
    374 	  g_string_append(result, "\\t");
    375 	  break;
    376 
    377 	default:
    378 	  g_string_append_c(result, c);
    379 	  break;
    380 	}
    381     }
    382 
    383   g_string_append_c(result, '"');
    384 
    385   return g_string_free(result, FALSE);
    386 }
    387 
    388 char *
    389 jb_strip_extension (const char *filename)
    390 {
    391   char *p;
    392 
    393   p = strrchr(filename, '.');
    394   if (p != NULL)
    395     return g_strndup(filename, p - filename);
    396   else
    397     return g_strdup(filename);
    398 }
    399 
    400 char *
    401 jb_strip_chars (const char *str, const char *chars)
    402 {
    403   const char *p;
    404   GString *result;
    405 
    406   g_return_val_if_fail(str != NULL, NULL);
    407   g_return_val_if_fail(chars != NULL, NULL);
    408 
    409   result = g_string_new(NULL);
    410 
    411   for (p = str; *p != '\0'; p++)
    412     {
    413       char c = *p;
    414 
    415       if (strchr(chars, c) == NULL)
    416 	g_string_append_c(result, c);
    417     }
    418 
    419   return g_string_free(result, FALSE);
    420 }
    421 
    422 char *
    423 jb_utf8_escape (const char *str)
    424 {
    425   GString *escaped;
    426 
    427   g_return_val_if_fail(str != NULL, NULL);
    428 
    429   escaped = g_string_new(NULL);
    430 
    431   while (*str != '\0')
    432     {
    433       gunichar c;
    434 
    435       c = g_utf8_get_char_validated(str, -1);
    436       if (c != (gunichar) -2 && c != (gunichar) -1)
    437 	{
    438 	  g_string_append_unichar(escaped, c);
    439 	  str = g_utf8_next_char(str);
    440 	}
    441       else
    442 	{
    443 	  g_string_append_printf(escaped, "\\x%02x", (unsigned int) (unsigned char) *str);
    444 	  str++;
    445 	}
    446     }
    447 
    448   return g_string_free(escaped, FALSE);
    449 }
    450 
    451 gboolean
    452 jb_parse_uint32 (const char *str, int base, guint32 *value, GError **err)
    453 {
    454   guint64 v;
    455 
    456   g_return_val_if_fail(str != NULL, FALSE);
    457 
    458   if (! jb_parse_uint64(str, base, &v, err))
    459     return FALSE;
    460 
    461   if (v > G_MAXUINT32)
    462     {
    463       g_set_error(err, 0, 0, "number out of range");
    464       return FALSE;
    465     }
    466 
    467   *value = v;
    468   return TRUE;
    469 }
    470 
    471 gboolean
    472 jb_parse_uint64 (const char *str, int base, guint64 *value, GError **err)
    473 {
    474   guint64 v;
    475   char *end;
    476 
    477   g_return_val_if_fail(str != NULL, FALSE);
    478 
    479   v = g_ascii_strtoull(str, &end, base);
    480 
    481   if (*end != '\0')
    482     {
    483       g_set_error(err, 0, 0, "invalid number");
    484       return FALSE;
    485     }
    486 
    487   if (v == G_MAXUINT64 && errno == ERANGE)
    488     {
    489       g_set_error(err, 0, 0, "number out of range");
    490       return FALSE;
    491     }
    492 
    493   *value = v;
    494   return TRUE;
    495 }
    496 
    497 gboolean
    498 jb_write_file (const char *filename, const char *contents, GError **err)
    499 {
    500   GIOChannel *channel;
    501   gsize bytes_written;
    502   gboolean status = FALSE;
    503 
    504   g_return_val_if_fail(filename != NULL, FALSE);
    505   g_return_val_if_fail(contents != NULL, FALSE);
    506 
    507   channel = g_io_channel_new_file(filename, "w", err);
    508   if (channel == NULL)
    509     return FALSE;
    510 
    511   if (g_io_channel_write_chars(channel, contents, -1, &bytes_written, err) == G_IO_STATUS_NORMAL)
    512     {
    513       if (g_io_channel_shutdown(channel, TRUE, err) == G_IO_STATUS_NORMAL)
    514 	status = TRUE;
    515     }
    516   else
    517     g_io_channel_shutdown(channel, FALSE, NULL);
    518 
    519   g_io_channel_unref(channel);
    520 
    521   return status;
    522 }
    523 
    524 void
    525 jb_write_file_or_exit (const char *filename, const char *contents)
    526 {
    527   GError *err = NULL;
    528 
    529   if (! jb_write_file(filename, contents, &err))
    530     jb_error("cannot write %s: %s", filename, err->message);
    531 }
    532 
    533 char *
    534 jb_read_file (const char *filename, GError **err)
    535 {
    536   GIOChannel *channel;
    537   char *contents = NULL;
    538   gsize length;
    539 
    540   g_return_val_if_fail(filename != NULL, FALSE);
    541 
    542   channel = g_io_channel_new_file(filename, "r", err);
    543   if (channel == NULL)
    544     return NULL;
    545 
    546   g_io_channel_read_to_end(channel, &contents, &length, err);
    547 
    548   g_io_channel_shutdown(channel, FALSE, NULL);
    549   g_io_channel_unref(channel);
    550 
    551   return contents;
    552 }
    553 
    554 char *
    555 jb_read_file_or_exit (const char *filename)
    556 {
    557   GError *err = NULL;
    558   char *contents;
    559 
    560   contents = jb_read_file(filename, &err);
    561   if (contents == NULL)
    562     jb_error("cannot read %s: %s", filename, err->message);
    563 
    564   return contents;
    565 }
    566 
    567 GSList *
    568 jb_match_files (const char *pattern)
    569 {
    570   glob_t glob_result;
    571 
    572   g_return_val_if_fail(pattern != NULL, NULL);
    573 
    574   if (glob(pattern, 0, NULL, &glob_result) == 0)
    575     {
    576       int i;
    577       GSList *files = NULL;
    578 
    579       for (i = 0; i < glob_result.gl_pathc; i++)
    580 	files = g_slist_append(files, g_strdup(glob_result.gl_pathv[i]));
    581 
    582       globfree(&glob_result);
    583 
    584       return files;
    585     }
    586   else
    587     return NULL;
    588 }
    589 
    590 void
    591 jb_chdir (const char *path)
    592 {
    593   g_return_if_fail(path != NULL);
    594 
    595   if (chdir(path) < 0)
    596     jb_error("cannot change directory to %s: %s", path, g_strerror(errno));
    597 }
    598 
    599 void
    600 jb_mkdir (const char *pathname)
    601 {
    602   g_return_if_fail(pathname != NULL);
    603 
    604   if (g_mkdir_with_parents(pathname, 0755) < 0)
    605     jb_error("cannot create directory %s: %s", pathname, g_strerror(errno));
    606 }
    607 
    608 void
    609 jb_mkdir_of_file (const char *filename)
    610 {
    611   char *dir;
    612 
    613   g_return_if_fail(filename != NULL);
    614 
    615   dir = g_path_get_dirname(filename);
    616   jb_mkdir(dir);
    617   g_free(dir);
    618 }
    619 
    620 void
    621 jb_rename (const char *oldpath, const char *newpath)
    622 {
    623   g_return_if_fail(oldpath != NULL);
    624   g_return_if_fail(newpath != NULL);
    625 
    626   if (rename(oldpath, newpath) < 0)
    627     jb_error("cannot rename %s to %s: %s", oldpath, newpath, g_strerror(errno));
    628 }
    629 
    630 void
    631 jb_chmod (const char *path, mode_t mode)
    632 {
    633   g_return_if_fail(path != NULL);
    634 
    635   if (chmod(path, mode) < 0)
    636     jb_error("cannot chmod %s to " JB_MODE_FORMAT ": %s", path, (unsigned int) mode, g_strerror(errno));
    637 }
    638 
    639 gboolean
    640 jb_fchown_by_name (int fd,
    641 		   const char *owner,
    642 		   const char *group,
    643 		   GError **err)
    644 {
    645   uid_t uid = -1;
    646   gid_t gid = -1;
    647 
    648   g_return_val_if_fail(fd >= 0, FALSE);
    649   g_return_val_if_fail(owner != NULL || group != NULL, FALSE);
    650 
    651   if (owner != NULL)
    652     {
    653       struct passwd *info;
    654 
    655       info = getpwnam(owner);
    656       if (info == NULL)
    657 	{
    658 	  g_set_error(err, 0, 0, "unknown user \"%s\"", owner);
    659 	  return FALSE;
    660 	}
    661 
    662       uid = info->pw_uid;
    663     }
    664 
    665   if (group != NULL)
    666     {
    667       struct group *info;
    668 
    669       info = getgrnam(group);
    670       if (group == NULL)
    671 	{
    672 	  g_set_error(err, 0, 0, "unknown group \"%s\"", group);
    673 	  return FALSE;
    674 	}
    675 
    676       gid = info->gr_gid;
    677     }
    678 
    679   if (fchown(fd, uid, gid) < 0)
    680     {
    681       g_set_error(err, 0, 0, "%s", g_strerror(errno));
    682       return FALSE;
    683     }
    684 
    685   return TRUE;
    686 }
    687 
    688 void
    689 jb_rmtree (const char *dir)
    690 {
    691   /*
    692    * Be paranoid and refuse abberant inputs. These tests are not
    693    * assertions to make sure that they won't be compiled out.
    694    */
    695   if (dir == NULL)
    696     g_error("dir is NULL");
    697   if (g_path_is_absolute(dir))
    698     g_error("refusing to rmtree an absolute path");
    699 
    700   jb_exec(NULL, NULL, "rm -rf %s", dir);
    701 }
    702 
    703 static char *
    704 subst_real (const char *str, GHashTable *variables)
    705 {
    706   GString *result;
    707   const char *p;
    708 
    709   result = g_string_new(NULL);
    710 
    711   for (p = str; *p != '\0';)
    712     {
    713       char c = *p;
    714 
    715       if (c == '@')
    716 	{
    717 	  const char *start;
    718 	  char *end;
    719 
    720 	  start = p + 1;
    721 	  end = strpbrk(start, "@\n");
    722 
    723 	  if (end != NULL && *end == '@')
    724 	    {
    725 	      char *name;
    726 	      const char *value;
    727 
    728 	      name = g_strndup(start, end - start);
    729 	      value = g_hash_table_lookup(variables, name);
    730 	      g_free(name);
    731 
    732 	      if (value != NULL)
    733 		{
    734 		  g_string_append(result, value);
    735 		  p = end + 1;
    736 		  continue;
    737 		}
    738 	    }
    739 	}
    740 
    741       g_string_append_c(result, c);
    742       p++;
    743     }
    744 
    745   return g_string_free(result, FALSE);
    746 }
    747 
    748 void
    749 jb_subst (const char *infile,
    750 	  const char *outfile,
    751 	  GHashTable *variables)
    752 {
    753   char *contents;
    754   char *result;
    755   char *tmp_outfile;
    756 
    757   g_return_if_fail(infile != NULL);
    758   g_return_if_fail(outfile != NULL);
    759   g_return_if_fail(variables != NULL);
    760 
    761   contents = jb_read_file_or_exit(infile);
    762   result = subst_real(contents, variables);
    763   g_free(contents);
    764 
    765   tmp_outfile = g_strdup_printf("%s.tmp", outfile);
    766   jb_write_file_or_exit(tmp_outfile, result);
    767   g_free(result);
    768 
    769   jb_rename(tmp_outfile, outfile);
    770   g_free(tmp_outfile);
    771 }
    772 
    773 static char *
    774 convert_process_output (const char *output)
    775 {
    776   char *utf8;
    777   char *no_nl;
    778 
    779   if (g_utf8_validate(output, -1, NULL))
    780     utf8 = g_strdup(output);
    781   else
    782     {
    783       utf8 = g_locale_to_utf8(output, -1, NULL, NULL, NULL);
    784       if (utf8 == NULL)
    785 	utf8 = jb_utf8_escape(output);
    786     }
    787 
    788   no_nl = jb_strip_newline(utf8);
    789   g_free(utf8);
    790 
    791   return no_nl;
    792 }
    793 
    794 /*
    795  * Returns TRUE if the command exited with status 0.
    796  *
    797  * The trailing newline of @standard_output and @standard_error will
    798  * be stripped.
    799  *
    800  * @standard_output and @standard_error will be set even if FALSE is
    801  * returned, since a command can produce an output even if it exits
    802  * with a non-zero status.
    803  */
    804 gboolean
    805 jb_exec (char **standard_output,
    806 	 char **standard_error,
    807 	 const char *format,
    808 	 ...)
    809 {
    810   char *command;
    811   int command_status;
    812   gboolean status = FALSE;
    813   char *_stdout;
    814   char *_stderr;
    815   char *converted_stdout = NULL;
    816   char *converted_stderr = NULL;
    817   char *shell_argv[4];
    818   GError *err = NULL;
    819 
    820   g_return_val_if_fail(format != NULL, FALSE);
    821 
    822   JB_STRDUP_VPRINTF(command, format);
    823 
    824   shell_argv[0] = "/bin/sh";
    825   shell_argv[1] = "-c";
    826   shell_argv[2] = command;
    827   shell_argv[3] = NULL;
    828 
    829   if (g_spawn_sync(NULL,
    830 		   shell_argv,
    831 		   NULL,
    832 		   G_SPAWN_SEARCH_PATH,
    833 		   NULL,
    834 		   NULL,
    835 		   &_stdout,
    836 		   &_stderr,
    837 		   &command_status,
    838 		   &err))
    839     {
    840       converted_stdout = convert_process_output(_stdout);
    841       g_free(_stdout);
    842 
    843       converted_stderr = convert_process_output(_stderr);
    844       g_free(_stderr);
    845 
    846       if (WIFEXITED(command_status))
    847 	{
    848 	  int exit_status;
    849 
    850 	  exit_status = WEXITSTATUS(command_status);
    851 	  if (exit_status == 0)
    852 	    {
    853 	      jb_log("command \"%s\" succeeded", command);
    854 	      status = TRUE;
    855 	    }
    856 	  else
    857 	    jb_log("command \"%s\" failed with status %i", command, exit_status);
    858 
    859 	  if (*converted_stdout != '\0')
    860 	    {
    861 	      jb_log("standard output:");
    862 	      jb_log("%s", converted_stdout);
    863 	      jb_log(JB_SEPARATOR);
    864 	    }
    865 	  if (*converted_stderr != '\0')
    866 	    {
    867 	      jb_log("standard error output:");
    868 	      jb_log("%s", converted_stderr);
    869 	      jb_log(JB_SEPARATOR);
    870 	    }
    871 	}
    872       else
    873 	jb_log("command exited abnormally");
    874     }
    875   else
    876     /* fatal error: it should not happend since we exec the shell */
    877     jb_error("cannot execute command \"%s\": %s", command, err->message);
    878 
    879   g_free(command);
    880 
    881   if (standard_output)
    882     *standard_output = converted_stdout;
    883   else
    884     g_free(converted_stdout);
    885 
    886   if (standard_error)
    887     *standard_error = converted_stderr;
    888   else
    889     g_free(converted_stderr);
    890 
    891   return status;
    892 }
    893 
    894 gboolean
    895 jb_exec_expand (char **standard_output,
    896 		char **standard_error,
    897 		const char *str,
    898 		...)
    899 {
    900   va_list args;
    901   char *command;
    902   gboolean result;
    903 
    904   g_return_val_if_fail(str != NULL, FALSE);
    905 
    906   va_start(args, str);
    907   command = jb_variable_expandv(str, args);
    908   va_end(args);
    909 
    910   result = jb_exec(standard_output, standard_error, "%s", command);
    911   g_free(command);
    912 
    913   return result;
    914 }
    915 
    916 JBStringHashSet *
    917 jb_string_hash_set_new (void)
    918 {
    919   return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
    920 }
    921 
    922 gboolean
    923 jb_string_hash_set_add (JBStringHashSet *set, const char *value)
    924 {
    925   g_return_val_if_fail(set != NULL, FALSE);
    926   g_return_val_if_fail(value != NULL, FALSE);
    927 
    928   if (g_hash_table_lookup(set, value) != NULL)
    929     return FALSE;
    930 
    931   g_hash_table_insert(set, g_strdup(value), GINT_TO_POINTER(TRUE));
    932   return TRUE;
    933 }
    934 
    935 gboolean
    936 jb_string_hash_set_contains (JBStringHashSet *set, const char *value)
    937 {
    938   g_return_val_if_fail(set != NULL, FALSE);
    939   g_return_val_if_fail(value != NULL, FALSE);
    940 
    941   return g_hash_table_lookup_extended(set, value, NULL, NULL);
    942 }
    943 
    944 gboolean
    945 jb_is_uptodate (const char *dst, const char *src)
    946 {
    947   struct stat dst_sb;
    948   struct stat src_sb;
    949 
    950   g_return_val_if_fail(dst != NULL, FALSE);
    951   g_return_val_if_fail(src != NULL, FALSE);
    952 
    953   if (stat(dst, &dst_sb) < 0)
    954     return FALSE;
    955 
    956   if (stat(src, &src_sb) < 0)
    957     g_error("%s (dependency of %s) does not exist", src, dst);
    958 
    959   return dst_sb.st_mtime >= src_sb.st_mtime;
    960 }
    961 
    962 gboolean
    963 jb_is_uptodate_list (const char *dst, GSList *src_list)
    964 {
    965   struct stat dst_sb;
    966   GSList *l;
    967 
    968   g_return_val_if_fail(dst != NULL, FALSE);
    969   g_return_val_if_fail(src_list != NULL, FALSE);
    970 
    971   if (stat(dst, &dst_sb) < 0)
    972     return FALSE;
    973 
    974   JB_LIST_FOREACH(l, src_list)
    975     {
    976       const char *src = l->data;
    977       struct stat src_sb;
    978 
    979       if (stat(src, &src_sb) < 0)
    980 	g_error("%s (dependency of %s) does not exist", src, dst);
    981 
    982       if (dst_sb.st_mtime < src_sb.st_mtime)
    983 	return FALSE;
    984     }
    985 
    986   return TRUE;
    987 }
    988 
    989 gboolean
    990 jb_is_uptodate_list_list (GSList *dst_list, GSList *src_list)
    991 {
    992   GArray *dst_mtimes;
    993   GSList *l;
    994   gboolean result = TRUE;
    995   const char *filename;
    996   struct stat sb;
    997 
    998   dst_mtimes = g_array_new(FALSE, FALSE, sizeof(time_t));
    999 
   1000   JB_LIST_FOREACH(l, dst_list)
   1001     {
   1002       filename = l->data;
   1003 
   1004       if (stat(filename, &sb) < 0)
   1005 	{
   1006 	  result = FALSE;
   1007 	  goto end;
   1008 	}
   1009 
   1010       g_array_append_val(dst_mtimes, sb.st_mtime);
   1011     }
   1012 
   1013   JB_LIST_FOREACH(l, src_list)
   1014     {
   1015       int i;
   1016 
   1017       filename = l->data;
   1018 
   1019       if (stat(filename, &sb) < 0)
   1020 	g_error("%s (dependency of %s) does not exist", filename, jb_string_list_join(dst_list, " "));
   1021 
   1022       for (i = 0; i < dst_mtimes->len; i++)
   1023 	{
   1024 	  time_t dst_mtime = g_array_index(dst_mtimes, time_t, i);
   1025 
   1026 	  if (dst_mtime < sb.st_mtime)
   1027 	    {
   1028 	      result = FALSE;
   1029 	      goto end;
   1030 	    }
   1031 	}
   1032     }
   1033 
   1034  end:
   1035   g_array_free(dst_mtimes, TRUE);
   1036   return result;
   1037 }
   1038 
   1039 char *
   1040 jb_string_list_join (GSList *list, const char *separator)
   1041 {
   1042   GString *result;
   1043   GSList *l;
   1044 
   1045   g_return_val_if_fail(separator != NULL, NULL);
   1046 
   1047   result = g_string_new(NULL);
   1048 
   1049   JB_LIST_FOREACH(l, list)
   1050     {
   1051       const char *str = l->data;
   1052 
   1053       g_string_append(result, str);
   1054 
   1055       if (l->next != NULL)
   1056 	g_string_append(result, separator);
   1057     }
   1058 
   1059   return g_string_free(result, FALSE);
   1060 }