]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
Merge branch 'maint'
authorJoel Rosdahl <joel@rosdahl.net>
Wed, 8 Jan 2014 20:28:40 +0000 (21:28 +0100)
committerJoel Rosdahl <joel@rosdahl.net>
Wed, 8 Jan 2014 20:28:40 +0000 (21:28 +0100)
* maint:
  Require CCACHE_SLOPPINESS=pch_defines,time_macros opt-in to enable PCH handling
  doc: Mention that --ccache-skip currently does not mean "skip hashing option"
  Don't hash compiler option -fdebug-prefix-map=

Conflicts:
MANUAL.txt
ccache.c
ccache.h
test.sh

1  2 
MANUAL.txt
ccache.c
ccache.h
conf.c
test.sh
test/test_conf.c

diff --cc MANUAL.txt
index 1c4ecb3793fae0f3cdd5bd71bd5ef5d6c0c880e4,5bc1ca3950943e87ded72f04b2d79b1be8685ed2..a36c5050387e591ffb1d0e1fa40a43ce5f6c3005
@@@ -425,18 -373,13 +428,21 @@@ WRAPPERS>>
  --
  *file_macro*::
      Ignore *\_\_FILE__* being present in the source.
- *include_file_mtime*::
-     By default, ccache will not cache a file if it includes a header whose
-     mtime is too new. This option disables that check.
++*file_stat_matches*::
++    ccache normally examines a file's contents to determine whether it matches
++    the cached version. With this option set, ccache will consider a file as
++    matching its cached version if the sizes, mtimes and ctimes match.
 +*include_file_ctime*::
 +    By default, ccache also will not cache a file if it includes a header whose
 +    ctime is too new. This option disables that check.
+ *include_file_mtime*::
 -    Don't check the modification time of include files in the direct mode.
++    By default, ccache will not cache a file if it includes a header whose
++    mtime is too new. This option disables that check.
+ *pch_defines*::
+     Be sloppy about #defines when precompiling a header file. See
+     <<_precompiled_headers,PRECOMPILED HEADERS>> for more information.
  *time_macros*::
      Ignore *\_\_DATE\__* and *\_\_TIME__* being present in the source code.
- *file_stat_matches*::
-     ccache normally examines a file's contents to determine whether it matches
-     the cached version. With this option set, ccache will consider a file as
-     matching its cached version if the sizes, mtimes and ctimes match.
  --
  +
  See the discussion under <<_troubleshooting,TROUBLESHOOTING>> for more
@@@ -635,9 -573,11 +641,11 @@@ Precompiled header
  ccache has support for GCC's precompiled headers. However, you have to do some
  things to make it work properly:
  
- * You must set *sloppiness* to *time_macros*. The reason is that ccache can't
-   tell whether *\_\_TIME\__* or *\_\_DATE__* is used when using a precompiled
-   header.
 -* You must set *CCACHE_SLOPPINESS* to *pch_defines,time_macros*. The reason is
 -  that ccache can't tell whether *\_\_TIME\__* or *\_\_DATE__* is used when
 -  using a precompiled header. Further, it can't detect changes in #defines in
 -  the source code because of how preprocessing works in combination with
++* You must set *sloppiness* to *pch_defines,time_macros*. The reason is that
++  ccache can't tell whether *\_\_TIME\__* or *\_\_DATE__* is used when using a
++  precompiled header. Further, it can't detect changes in #defines in the
++  source code because of how preprocessing works in combination with
+   precompiled headers.
  * You must either:
  +
  --
diff --cc ccache.c
index 4b63c68c4f2d053e4b07660a57b946eda788ec5b,dc5498d67a1a6685ac4e1fbd673bcb11fc0d5706..c395fadfdede09d331efd43e6135cefdb676f2b9
+++ b/ccache.c
@@@ -2177,9 -1705,10 +2184,10 @@@ cc_process_args(struct args *args, stru
  
        if (found_pch || found_fpch_preprocess) {
                using_precompiled_header = true;
-               if (!(conf->sloppiness & SLOPPY_TIME_MACROS)) {
 -              if (!(sloppiness & SLOPPY_PCH_DEFINES)
 -                  || !(sloppiness & SLOPPY_TIME_MACROS)) {
 -                      cc_log("You have to specify \"pch_defines,time_macros\" sloppiness when"
 -                             " using precompiled headers to get direct hits");
++              if (!(conf->sloppiness & SLOPPY_PCH_DEFINES)
++                  || !(conf->sloppiness & SLOPPY_TIME_MACROS)) {
 +                      cc_log("You have to specify \"time_macros\" sloppiness when using"
 +                             " precompiled headers to get direct hits");
                        cc_log("Disabling direct mode");
                        stats_update(STATS_CANTUSEPCH);
                        result = false;
diff --cc ccache.h
index 48812df05556ba026bcd4178de701711dcbbb63b,282f9811a86537e15ecc792c8233eed48a6bdfba..1d86a9dc746ae60c33840fa6505d3ebaffc6b080
+++ b/ccache.h
@@@ -54,15 -53,9 +54,15 @@@ enum stats 
  };
  
  #define SLOPPY_INCLUDE_FILE_MTIME 1
 -#define SLOPPY_FILE_MACRO 2
 -#define SLOPPY_TIME_MACROS 4
 -#define SLOPPY_PCH_DEFINES 8
 +#define SLOPPY_INCLUDE_FILE_CTIME 2
 +#define SLOPPY_FILE_MACRO 4
 +#define SLOPPY_TIME_MACROS 8
++#define SLOPPY_PCH_DEFINES 16
 +/*
 + * Allow us to match files based on their stats (size, mtime, ctime), without
 + * looking at their contents.
 + */
- #define SLOPPY_FILE_STAT_MATCHES 16
++#define SLOPPY_FILE_STAT_MATCHES 32
  
  #define str_eq(s1, s2) (strcmp((s1), (s2)) == 0)
  #define str_startswith(s, p) (strncmp((s), (p), strlen((p))) == 0)
diff --cc conf.c
index b49ba3c87577534ade070f699255dbb9b9cf1f42,0000000000000000000000000000000000000000..0252f5cf8a295f160c57f7c48c21a286377c1884
mode 100644,000000..100644
--- 1/conf.c
--- /dev/null
+++ b/conf.c
@@@ -1,624 -1,0 +1,626 @@@
-  * Copyright (C) 2011-2013 Joel Rosdahl
 +/*
-               } else if (str_eq(word, "include_file_mtime")) {
-                       *value |= SLOPPY_INCLUDE_FILE_MTIME;
++ * Copyright (C) 2011-2014 Joel Rosdahl
 + *
 + * This program is free software; you can redistribute it and/or modify it
 + * under the terms of the GNU General Public License as published by the Free
 + * Software Foundation; either version 3 of the License, or (at your option)
 + * any later version.
 + *
 + * This program is distributed in the hope that it will be useful, but WITHOUT
 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 + * more details.
 + *
 + * You should have received a copy of the GNU General Public License along with
 + * this program; if not, write to the Free Software Foundation, Inc., 51
 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 + */
 +
 +#include "conf.h"
 +#include "ccache.h"
 +
 +typedef bool (*conf_item_parser)(const char *str, void *result, char **errmsg);
 +typedef bool (*conf_item_verifier)(void *value, char **errmsg);
 +
 +struct conf_item {
 +      const char *name;
 +      size_t number;
 +      conf_item_parser parser;
 +      size_t offset;
 +      conf_item_verifier verifier;
 +};
 +
 +struct env_to_conf_item {
 +      const char *env_name;
 +      const char *conf_name;
 +};
 +
 +static bool
 +parse_bool(const char *str, void *result, char **errmsg)
 +{
 +      bool *value = (bool *)result;
 +
 +      if (str_eq(str, "true")) {
 +              *value = true;
 +              return true;
 +      } else if (str_eq(str, "false")) {
 +              *value = false;
 +              return true;
 +      } else {
 +              *errmsg = format("not a boolean value: \"%s\"", str);
 +              return false;
 +      }
 +}
 +
 +static bool
 +parse_env_string(const char *str, void *result, char **errmsg)
 +{
 +      char **value = (char **)result;
 +      free(*value);
 +      *value = subst_env_in_string(str, errmsg);
 +      return *value;
 +}
 +
 +static bool
 +parse_size(const char *str, void *result, char **errmsg)
 +{
 +      uint64_t *value = (uint64_t *)result;
 +      uint64_t size;
 +      *errmsg = NULL;
 +      if (parse_size_with_suffix(str, &size)) {
 +              *value = size;
 +              return true;
 +      } else {
 +              *errmsg = format("invalid size: \"%s\"", str);
 +              return false;
 +      }
 +}
 +
 +static bool
 +parse_sloppiness(const char *str, void *result, char **errmsg)
 +{
 +      unsigned *value = (unsigned *)result;
 +      char *word, *p, *q, *saveptr = NULL;
 +
 +      if (!str) {
 +              return *value;
 +      }
 +      p = x_strdup(str);
 +      q = p;
 +      while ((word = strtok_r(q, ", ", &saveptr))) {
 +              if (str_eq(word, "file_macro")) {
 +                      *value |= SLOPPY_FILE_MACRO;
-               } else if (str_eq(word, "file_stat_matches")) {
-                       *value |= SLOPPY_FILE_STAT_MATCHES;
++              } else if (str_eq(word, "file_stat_matches")) {
++                      *value |= SLOPPY_FILE_STAT_MATCHES;
 +              } else if (str_eq(word, "include_file_ctime")) {
 +                      *value |= SLOPPY_INCLUDE_FILE_CTIME;
++              } else if (str_eq(word, "include_file_mtime")) {
++                      *value |= SLOPPY_INCLUDE_FILE_MTIME;
++              } else if (str_eq(word, "pch_defines")) {
++                      *value |= SLOPPY_PCH_DEFINES;
 +              } else if (str_eq(word, "time_macros")) {
 +                      *value |= SLOPPY_TIME_MACROS;
 +              } else {
 +                      *errmsg = format("unknown sloppiness: \"%s\"", word);
 +                      free(p);
 +                      return false;
 +              }
 +              q = NULL;
 +      }
 +      free(p);
 +      return true;
 +}
 +
 +static bool
 +parse_string(const char *str, void *result, char **errmsg)
 +{
 +      char **value = (char **)result;
 +      (void)errmsg;
 +      free(*value);
 +      *value = x_strdup(str);
 +      return true;
 +}
 +
 +static bool
 +parse_umask(const char *str, void *result, char **errmsg)
 +{
 +      unsigned *value = (unsigned *)result;
 +      char *endptr;
 +      if (str_eq(str, "")) {
 +              *value = UINT_MAX;
 +              return true;
 +      }
 +      errno = 0;
 +      *value = strtoul(str, &endptr, 8);
 +      if (errno == 0 && *str != '\0' && *endptr == '\0') {
 +              return true;
 +      } else {
 +              *errmsg = format("not an octal integer: \"%s\"", str);
 +              return false;
 +      }
 +}
 +
 +static bool
 +parse_unsigned(const char *str, void *result, char **errmsg)
 +{
 +      unsigned *value = (unsigned *)result;
 +      long x;
 +      char *endptr;
 +      errno = 0;
 +      x = strtol(str, &endptr, 10);
 +      if (errno == 0 && x >= 0 && *str != '\0' && *endptr == '\0') {
 +              *value = x;
 +              return true;
 +      } else {
 +              *errmsg = format("invalid unsigned integer: \"%s\"", str);
 +              return false;
 +      }
 +}
 +
 +static bool
 +verify_absolute_path(void *value, char **errmsg)
 +{
 +      char **path = (char **)value;
 +      assert(*path);
 +      if (str_eq(*path, "")) {
 +              /* The empty string means "disable" in this case. */
 +              return true;
 +      } else if (is_absolute_path(*path)) {
 +              return true;
 +      } else {
 +              *errmsg = format("not an absolute path: \"%s\"", *path);
 +              return false;
 +      }
 +}
 +
 +static bool
 +verify_dir_levels(void *value, char **errmsg)
 +{
 +      unsigned *levels = (unsigned *)value;
 +      assert(levels);
 +      if (*levels >= 1 && *levels <= 8) {
 +              return true;
 +      } else {
 +              *errmsg = format("cache directory levels must be between 1 and 8");
 +              return false;
 +      }
 +}
 +
 +#define ITEM(name, type) \
 +      parse_##type, offsetof(struct conf, name), NULL
 +#define ITEM_V(name, type, verification) \
 +      parse_##type, offsetof(struct conf, name), verify_##verification
 +
 +#include "confitems_lookup.c"
 +#include "envtoconfitems_lookup.c"
 +
 +static const struct conf_item *
 +find_conf(const char *name)
 +{
 +      return confitems_get(name, strlen(name));
 +}
 +
 +static const struct env_to_conf_item *
 +find_env_to_conf(const char *name)
 +{
 +      return envtoconfitems_get(name, strlen(name));
 +}
 +
 +static bool
 +handle_conf_setting(struct conf *conf, const char *key, const char *value,
 +                    char **errmsg, bool from_env_variable, bool negate_boolean,
 +                    const char *origin)
 +{
 +      const struct conf_item *item;
 +
 +      item = find_conf(key);
 +      if (!item) {
 +              *errmsg = format("unknown configuration option \"%s\"", key);
 +              return false;
 +      }
 +
 +      if (from_env_variable && item->parser == parse_bool) {
 +              /*
 +               * Special rule for boolean settings from the environment: any value means
 +               * true.
 +               */
 +              bool *value = (bool *)((char *)conf + item->offset);
 +              *value = !negate_boolean;
 +              goto out;
 +      }
 +
 +      if (!item->parser(value, (char *)conf + item->offset, errmsg)) {
 +              return false;
 +      }
 +      if (item->verifier && !item->verifier((char *)conf + item->offset, errmsg)) {
 +              return false;
 +      }
 +
 +out:
 +      conf->item_origins[item->number] = origin;
 +      return true;
 +}
 +
 +static bool
 +parse_line(const char *line, char **key, char **value, char **errmsg)
 +{
 +      const char *p, *q;
 +
 +#define SKIP_WS(x) while (isspace(*x)) { ++x; }
 +
 +      *key = NULL;
 +      *value = NULL;
 +
 +      p = line;
 +      SKIP_WS(p);
 +      if (*p == '\0' || *p == '#') {
 +              return true;
 +      }
 +      q = p;
 +      while (isalpha(*q) || *q == '_') {
 +              ++q;
 +      }
 +      *key = x_strndup(p, q - p);
 +      p = q;
 +      SKIP_WS(p);
 +      if (*p != '=') {
 +              *errmsg = x_strdup("missing equal sign");
 +              free(*key);
 +              *key = NULL;
 +              return false;
 +      }
 +      ++p;
 +
 +      /* Skip leading whitespace. */
 +      SKIP_WS(p);
 +      q = p;
 +      while (*q) {
 +              ++q;
 +      }
 +      /* Skip trailing whitespace. */
 +      while (isspace(q[-1])) {
 +              --q;
 +      }
 +      *value = x_strndup(p, q - p);
 +
 +      return true;
 +
 +#undef SKIP_WS
 +}
 +
 +/* Create a conf struct with default values. */
 +struct conf *
 +conf_create(void)
 +{
 +      size_t i;
 +      struct conf *conf = x_malloc(sizeof(*conf));
 +      conf->base_dir = x_strdup("");
 +      conf->cache_dir = format("%s/.ccache", get_home_directory());
 +      conf->cache_dir_levels = 2;
 +      conf->compiler = x_strdup("");
 +      conf->compiler_check = x_strdup("mtime");
 +      conf->compression = false;
 +      conf->compression_level = 6;
 +      conf->cpp_extension = x_strdup("");
 +      conf->direct_mode = true;
 +      conf->disable = false;
 +      conf->extra_files_to_hash = x_strdup("");
 +      conf->hard_link = false;
 +      conf->hash_dir = false;
 +      conf->log_file = x_strdup("");
 +      conf->max_files = 0;
 +      conf->max_size = (uint64_t)5 * 1000 * 1000 * 1000;
 +      conf->path = x_strdup("");
 +      conf->prefix_command = x_strdup("");
 +      conf->read_only = false;
 +      conf->recache = false;
 +      conf->run_second_cpp = false;
 +      conf->sloppiness = 0;
 +      conf->stats = true;
 +      conf->temporary_dir = x_strdup("");
 +      conf->umask = UINT_MAX; /* default: don't set umask */
 +      conf->unify = false;
 +      conf->item_origins = x_malloc(CONFITEMS_TOTAL_KEYWORDS * sizeof(char *));
 +      for (i = 0; i < CONFITEMS_TOTAL_KEYWORDS; ++i) {
 +              conf->item_origins[i] = "default";
 +      }
 +      return conf;
 +}
 +
 +void
 +conf_free(struct conf *conf)
 +{
 +      if (!conf) {
 +              return;
 +      }
 +      free(conf->base_dir);
 +      free(conf->cache_dir);
 +      free(conf->compiler);
 +      free(conf->compiler_check);
 +      free(conf->cpp_extension);
 +      free(conf->extra_files_to_hash);
 +      free(conf->log_file);
 +      free(conf->path);
 +      free(conf->prefix_command);
 +      free(conf->temporary_dir);
 +      free(conf->item_origins);
 +      free(conf);
 +}
 +
 +/* Note: The path pointer is stored in conf, so path must outlive conf. */
 +bool
 +conf_read(struct conf *conf, const char *path, char **errmsg)
 +{
 +      FILE *f;
 +      char buf[10000];
 +      bool result = true;
 +      unsigned line_number;
 +
 +      assert(errmsg);
 +      *errmsg = NULL;
 +
 +      f = fopen(path, "r");
 +      if (!f) {
 +              *errmsg = format("%s: %s", path, strerror(errno));
 +              return false;
 +      }
 +
 +      line_number = 0;
 +      while (fgets(buf, sizeof(buf), f)) {
 +              char *errmsg2, *key, *value;
 +              bool ok;
 +              ++line_number;
 +              ok = parse_line(buf, &key, &value, &errmsg2);
 +              if (ok && key) { /* key == NULL if comment or blank line */
 +                      ok = handle_conf_setting(conf, key, value, &errmsg2, false, false, path);
 +              }
 +              free(key);
 +              free(value);
 +              if (!ok) {
 +                      *errmsg = format("%s:%u: %s", path, line_number, errmsg2);
 +                      free(errmsg2);
 +                      result = false;
 +                      goto out;
 +              }
 +      }
 +      if (ferror(f)) {
 +              *errmsg = x_strdup(strerror(errno));
 +              result = false;
 +      }
 +
 +out:
 +      fclose(f);
 +      return result;
 +}
 +
 +bool
 +conf_update_from_environment(struct conf *conf, char **errmsg)
 +{
 +      char **p;
 +      char *q;
 +      char *key;
 +      char *errmsg2;
 +      const struct env_to_conf_item *env_to_conf_item;
 +      bool negate;
 +      size_t key_start;
 +
 +      for (p = environ; *p; ++p) {
 +              if (!str_startswith(*p, "CCACHE_")) {
 +                      continue;
 +              }
 +              q = strchr(*p, '=');
 +              if (!q) {
 +                      continue;
 +              }
 +
 +              if (str_startswith(*p + 7, "NO")) {
 +                      negate = true;
 +                      key_start = 9;
 +              } else {
 +                      negate = false;
 +                      key_start = 7;
 +              }
 +              key = x_strndup(*p + key_start, q - *p - key_start);
 +
 +              ++q; /* Now points to the value. */
 +
 +              env_to_conf_item = find_env_to_conf(key);
 +              if (!env_to_conf_item) {
 +                      free(key);
 +                      continue;
 +              }
 +
 +              if (!handle_conf_setting(
 +                          conf, env_to_conf_item->conf_name, q, &errmsg2, true, negate,
 +                          "environment")) {
 +                      *errmsg = format("%s: %s", key, errmsg2);
 +                      free(errmsg2);
 +                      free(key);
 +                      return false;
 +              }
 +
 +              free(key);
 +      }
 +
 +      return true;
 +}
 +
 +bool
 +conf_set_value_in_file(const char *path, const char *key, const char *value,
 +                       char **errmsg)
 +{
 +      FILE *infile, *outfile;
 +      char *outpath;
 +      char buf[10000];
 +      bool found;
 +      const struct conf_item *item;
 +
 +      item = find_conf(key);
 +      if (!item) {
 +              *errmsg = format("unknown configuration option \"%s\"", key);
 +              return false;
 +      }
 +
 +      infile = fopen(path, "r");
 +      if (!infile) {
 +              *errmsg = format("%s: %s", path, strerror(errno));
 +              return false;
 +      }
 +
 +      outpath = format("%s.tmp.%s", path, tmp_string());
 +      outfile = fopen(outpath, "w");
 +      if (!outfile) {
 +              *errmsg = format("%s: %s", outpath, strerror(errno));
 +              free(outpath);
 +              fclose(infile);
 +              return false;
 +      }
 +
 +      found = false;
 +      while (fgets(buf, sizeof(buf), infile)) {
 +              char *errmsg2, *key2, *value2;
 +              bool ok;
 +              ok = parse_line(buf, &key2, &value2, &errmsg2);
 +              if (ok && key2 && str_eq(key2, key)) {
 +                      found = true;
 +                      fprintf(outfile, "%s = %s\n", key, value);
 +              } else {
 +                      fputs(buf, outfile);
 +              }
 +              free(key2);
 +              free(value2);
 +      }
 +
 +      if (!found) {
 +              fprintf(outfile, "%s = %s\n", key, value);
 +      }
 +
 +      fclose(infile);
 +      fclose(outfile);
 +      if (x_rename(outpath, path) != 0) {
 +              *errmsg = format("rename %s to %s: %s", outpath, path, strerror(errno));
 +              return false;
 +      }
 +      free(outpath);
 +
 +      return true;
 +}
 +
 +bool
 +conf_print_items(struct conf *conf,
 +                 void (*printer)(const char *descr, const char *origin,
 +                                 void *context),
 +                 void *context)
 +{
 +      char *s = x_strdup("");
 +      char *s2;
 +
 +      reformat(&s, "base_dir = %s", conf->base_dir);
 +      printer(s, conf->item_origins[find_conf("base_dir")->number], context);
 +
 +      reformat(&s, "cache_dir = %s", conf->cache_dir);
 +      printer(s, conf->item_origins[find_conf("cache_dir")->number], context);
 +
 +      reformat(&s, "cache_dir_levels = %u", conf->cache_dir_levels);
 +      printer(s, conf->item_origins[find_conf("cache_dir_levels")->number],
 +              context);
 +
 +      reformat(&s, "compiler = %s", conf->compiler);
 +      printer(s, conf->item_origins[find_conf("compiler")->number], context);
 +
 +      reformat(&s, "compiler_check = %s", conf->compiler_check);
 +      printer(s, conf->item_origins[find_conf("compiler_check")->number], context);
 +
 +      reformat(&s, "compression = %s", conf->compression ? "true" : "false");
 +      printer(s, conf->item_origins[find_conf("compression")->number], context);
 +
 +      reformat(&s, "compression_level = %u", conf->compression_level);
 +      printer(s, conf->item_origins[find_conf("compression_level")->number],
 +              context);
 +
 +      reformat(&s, "cpp_extension = %s", conf->cpp_extension);
 +      printer(s, conf->item_origins[find_conf("cpp_extension")->number], context);
 +
 +      reformat(&s, "direct_mode = %s", conf->direct_mode ? "true" : "false");
 +      printer(s, conf->item_origins[find_conf("direct_mode")->number], context);
 +
 +      reformat(&s, "disable = %s", conf->disable ? "true" : "false");
 +      printer(s, conf->item_origins[find_conf("disable")->number], context);
 +
 +      reformat(&s, "extra_files_to_hash = %s", conf->extra_files_to_hash);
 +      printer(s, conf->item_origins[find_conf("extra_files_to_hash")->number],
 +              context);
 +
 +      reformat(&s, "hard_link = %s", conf->hard_link ? "true" : "false");
 +      printer(s, conf->item_origins[find_conf("hard_link")->number], context);
 +
 +      reformat(&s, "hash_dir = %s", conf->hash_dir ? "true" : "false");
 +      printer(s, conf->item_origins[find_conf("hash_dir")->number], context);
 +
 +      reformat(&s, "log_file = %s", conf->log_file);
 +      printer(s, conf->item_origins[find_conf("log_file")->number], context);
 +
 +      reformat(&s, "max_files = %u", conf->max_files);
 +      printer(s, conf->item_origins[find_conf("max_files")->number], context);
 +
 +      s2 = format_parsable_size_with_suffix(conf->max_size);
 +      reformat(&s, "max_size = %s", s2);
 +      printer(s, conf->item_origins[find_conf("max_size")->number], context);
 +      free(s2);
 +
 +      reformat(&s, "path = %s", conf->path);
 +      printer(s, conf->item_origins[find_conf("path")->number], context);
 +
 +      reformat(&s, "prefix_command = %s", conf->prefix_command);
 +      printer(s, conf->item_origins[find_conf("prefix_command")->number], context);
 +
 +      reformat(&s, "read_only = %s", conf->read_only ? "true" : "false");
 +      printer(s, conf->item_origins[find_conf("read_only")->number], context);
 +
 +      reformat(&s, "recache = %s", conf->recache ? "true" : "false");
 +      printer(s, conf->item_origins[find_conf("recache")->number], context);
 +
 +      reformat(&s, "run_second_cpp = %s", conf->run_second_cpp ? "true" : "false");
 +      printer(s, conf->item_origins[find_conf("run_second_cpp")->number], context);
 +
 +      reformat(&s, "sloppiness = ");
 +      if (conf->sloppiness & SLOPPY_FILE_MACRO) {
 +              reformat(&s, "%sfile_macro, ", s);
 +      }
 +      if (conf->sloppiness & SLOPPY_INCLUDE_FILE_MTIME) {
 +              reformat(&s, "%sinclude_file_mtime, ", s);
 +      }
 +      if (conf->sloppiness & SLOPPY_INCLUDE_FILE_CTIME) {
 +              reformat(&s, "%sinclude_file_ctime, ", s);
 +      }
 +      if (conf->sloppiness & SLOPPY_TIME_MACROS) {
 +              reformat(&s, "%stime_macros, ", s);
 +      }
 +      if (conf->sloppiness & SLOPPY_FILE_STAT_MATCHES) {
 +              reformat(&s, "%sfile_stat_matches, ", s);
 +      }
 +      if (conf->sloppiness) {
 +              /* Strip last ", ". */
 +              s[strlen(s) - 2] = '\0';
 +      }
 +      printer(s, conf->item_origins[find_conf("sloppiness")->number], context);
 +
 +      reformat(&s, "stats = %s", conf->stats ? "true" : "false");
 +      printer(s, conf->item_origins[find_conf("stats")->number], context);
 +
 +      reformat(&s, "temporary_dir = %s", conf->temporary_dir);
 +      printer(s, conf->item_origins[find_conf("temporary_dir")->number], context);
 +
 +      if (conf->umask == UINT_MAX) {
 +              reformat(&s, "umask = ");
 +      } else {
 +              reformat(&s, "umask = %03o", conf->umask);
 +      }
 +      printer(s, conf->item_origins[find_conf("umask")->number], context);
 +
 +      reformat(&s, "unify = %s", conf->unify ? "true" : "false");
 +      printer(s, conf->item_origins[find_conf("unify")->number], context);
 +
 +      free(s);
 +      return true;
 +}
diff --cc test.sh
index b8d1165805b6e9fdc5f4826f20f6cf11f45313c0,b4360e81da89968df685e561e765672c56f01ce6..51b9c882288932ef324f85cef867cfeaf29c9761
+++ b/test.sh
  #define time __TIME__
  int test;
  EOF
-     CCACHE_SLOPPINESS="$default_sloppiness time_macros" $CCACHE $COMPILER -c time.c
 -    CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER -c time.c
++    CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER -c time.c
      checkstat 'cache hit (direct)' 0
      checkstat 'cache hit (preprocessed)' 0
      checkstat 'cache miss' 1
-     CCACHE_SLOPPINESS="$default_sloppiness time_macros" $CCACHE $COMPILER -c time.c
 -    CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER -c time.c
++    CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER -c time.c
      checkstat 'cache hit (direct)' 1
      checkstat 'cache hit (preprocessed)' 0
      checkstat 'cache miss' 1
      cat <<EOF >time_h.c
  #include "time.h"
  EOF
-     CCACHE_SLOPPINESS="$default_sloppiness time_macros" $CCACHE $COMPILER -c time_h.c
 -    CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER -c time_h.c
++    CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER -c time_h.c
      checkstat 'cache hit (direct)' 0
      checkstat 'cache hit (preprocessed)' 0
      checkstat 'cache miss' 1
-     CCACHE_SLOPPINESS="$default_sloppiness time_macros" $CCACHE $COMPILER -c time_h.c
 -    CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER -c time_h.c
++    CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER -c time_h.c
      checkstat 'cache hit (direct)' 1
      checkstat 'cache hit (preprocessed)' 0
      checkstat 'cache miss' 1
@@@ -1975,9 -1761,9 +1975,9 @@@ gcc_pch_suite() 
      # trying to preprocess:
      checkstat 'preprocessor error' 1
  
-     testname="no -fpch-preprocess, -include, no sloppy time macros"
+     testname="no -fpch-preprocess, -include, no sloppiness"
      $CCACHE -Cz >/dev/null
 -    $CCACHE $COMPILER -c -include pch.h pch2.c 2>/dev/null
 +    $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
      checkstat 'cache hit (direct)' 0
      checkstat 'cache hit (preprocessed)' 0
      checkstat 'cache miss' 0
  
      testname="no -fpch-preprocess, -include"
      $CCACHE -Cz >/dev/null
-     CCACHE_SLOPPINESS="$default_sloppiness time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
 -    CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER -c -include pch.h pch2.c 2>/dev/null
++    CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
      checkstat 'cache hit (direct)' 0
      checkstat 'cache hit (preprocessed)' 0
      checkstat 'cache miss' 1
-     CCACHE_SLOPPINESS="$default_sloppiness time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
 -    CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER -c -include pch.h pch2.c 2>/dev/null
++    CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
      checkstat 'cache hit (direct)' 1
      checkstat 'cache hit (preprocessed)' 0
      checkstat 'cache miss' 1
  
-     testname="-fpch-preprocess, #include, no sloppy time macros"
+     testname="-fpch-preprocess, #include, no sloppiness"
      $CCACHE -Cz >/dev/null
 -    $CCACHE $COMPILER -c -fpch-preprocess pch.c
 +    $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
      checkstat 'cache hit (direct)' 0
      checkstat 'cache hit (preprocessed)' 0
      # Must enable sloppy time macros:
      checkstat "can't use precompiled header" 1
  
-     testname="-fpch-preprocess, #include, sloppy time macros"
+     testname="-fpch-preprocess, #include, sloppiness"
      $CCACHE -Cz >/dev/null
-     CCACHE_SLOPPINESS="$default_sloppiness time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
 -    CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER -c -fpch-preprocess pch.c
++    CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
      checkstat 'cache hit (direct)' 0
      checkstat 'cache hit (preprocessed)' 0
      checkstat 'cache miss' 1
-     CCACHE_SLOPPINESS="$default_sloppiness time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
 -    CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER -c -fpch-preprocess pch.c
++    CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
      checkstat 'cache hit (direct)' 1
      checkstat 'cache hit (preprocessed)' 0
      checkstat 'cache miss' 1
      testname="-fpch-preprocess, #include, file changed"
      echo "updated" >>pch.h.gch # GCC seems to cope with this...
      backdate pch.h.gch
-     CCACHE_SLOPPINESS="$default_sloppiness time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
 -    CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER -c -fpch-preprocess pch.c
++    CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
      checkstat 'cache hit (direct)' 1
      checkstat 'cache hit (preprocessed)' 0
      checkstat 'cache miss' 2
  
      testname="preprocessor mode"
      $CCACHE -Cz >/dev/null
-     CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
 -    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER -c -fpch-preprocess pch.c
++    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
      checkstat 'cache hit (direct)' 0
      checkstat 'cache hit (preprocessed)' 0
      checkstat 'cache miss' 1
-     CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
 -    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER -c -fpch-preprocess pch.c
++    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
      checkstat 'cache hit (direct)' 0
      checkstat 'cache hit (preprocessed)' 1
      checkstat 'cache miss' 1
      testname="preprocessor mode, file changed"
      echo "updated" >>pch.h.gch # GCC seems to cope with this...
      backdate pch.h.gch
-     CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
 -    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER -c -fpch-preprocess pch.c
++    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
 +    checkstat 'cache hit (direct)' 0
 +    checkstat 'cache hit (preprocessed)' 1
 +    checkstat 'cache miss' 2
-     CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
++    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
 +    checkstat 'cache hit (direct)' 0
 +    checkstat 'cache hit (preprocessed)' 2
 +    checkstat 'cache miss' 2
 +}
 +
 +clang_pch_suite() {
 +    ##################################################################
 +    # Tests for creating a .gch.
 +
 +    backdate pch.h
 +
 +    testname="create .gch, -c, no -o"
 +    $CCACHE -zC >/dev/null
 +    $CCACHE $COMPILER $SYSROOT -c pch.h
 +    checkstat 'cache hit (direct)' 0
 +    checkstat 'cache hit (preprocessed)' 0
 +    checkstat 'cache miss' 1
 +    rm -f pch.h.gch
 +    $CCACHE $COMPILER $SYSROOT -c pch.h
 +    checkstat 'cache hit (direct)' 1
 +    checkstat 'cache hit (preprocessed)' 0
 +    checkstat 'cache miss' 1
 +    if [ ! -f pch.h.gch ]; then
 +        test_failed "pch.h.gch missing"
 +    fi
 +
 +    testname="create .gch, no -c, -o"
 +    $CCACHE -Cz >/dev/null
 +    $CCACHE $COMPILER $SYSROOT pch.h -o pch.gch
 +    checkstat 'cache hit (direct)' 0
 +    checkstat 'cache hit (preprocessed)' 0
 +    checkstat 'cache miss' 1
 +    $CCACHE $COMPILER $SYSROOT pch.h -o pch.gch
 +    checkstat 'cache hit (direct)' 1
 +    checkstat 'cache hit (preprocessed)' 0
 +    checkstat 'cache miss' 1
 +    if [ ! -f pch.gch ]; then
 +        test_failed "pch.gch missing"
 +    fi
 +    rm pch.gch
 +
 +    ##################################################################
 +    # Tests for using a .gch.
 +
 +    backdate pch.h.gch
 +
 +    testname="gch, no -fpch-preprocess, -include, no sloppy time macros"
 +    $CCACHE -Cz >/dev/null
 +    $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
 +    checkstat 'cache hit (direct)' 0
 +    checkstat 'cache hit (preprocessed)' 0
 +    checkstat 'cache miss' 0
 +    # Must enable sloppy time macros:
 +    checkstat "can't use precompiled header" 1
 +
 +    testname="gch, no -fpch-preprocess, -include, sloppy time macros"
 +    $CCACHE -Cz >/dev/null
 +    CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
 +    checkstat 'cache hit (direct)' 0
 +    checkstat 'cache hit (preprocessed)' 0
 +    checkstat 'cache miss' 1
 +    CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
 +    checkstat 'cache hit (direct)' 1
 +    checkstat 'cache hit (preprocessed)' 0
 +    checkstat 'cache miss' 1
 +
 +    testname="gch, -fpch-preprocess, -include, file changed"
 +    echo "updated" >>pch.h.gch # clang seems to cope with this...
 +    backdate pch.h.gch
 +    CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
 +    checkstat 'cache hit (direct)' 1
 +    checkstat 'cache hit (preprocessed)' 0
 +    checkstat 'cache miss' 2
 +
 +    testname="gch, preprocessor mode"
 +    $CCACHE -Cz >/dev/null
 +    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
 +    checkstat 'cache hit (direct)' 0
 +    checkstat 'cache hit (preprocessed)' 0
 +    checkstat 'cache miss' 1
 +    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
 +    checkstat 'cache hit (direct)' 0
 +    checkstat 'cache hit (preprocessed)' 1
 +    checkstat 'cache miss' 1
 +
 +    testname="gch, preprocessor mode, file changed"
 +    echo "updated" >>pch.h.gch # clang seems to cope with this...
 +    backdate pch.h.gch
 +    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
 +    checkstat 'cache hit (direct)' 0
 +    checkstat 'cache hit (preprocessed)' 1
 +    checkstat 'cache miss' 2
 +    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
 +    checkstat 'cache hit (direct)' 0
 +    checkstat 'cache hit (preprocessed)' 2
 +    checkstat 'cache miss' 2
 +
 +    rm pch.h.gch
 +
 +    ##################################################################
 +    # Tests for creating a .pth.
 +
 +    backdate pch.h
 +
 +    testname="create .pth, -c, -o"
 +    $CCACHE -zC >/dev/null
 +    $CCACHE $COMPILER $SYSROOT -c pch.h -o pch.h.pth
 +    checkstat 'cache hit (direct)' 0
 +    checkstat 'cache hit (preprocessed)' 0
 +    checkstat 'cache miss' 1
 +    rm -f pch.h.pth
 +    $CCACHE $COMPILER $SYSROOT -c pch.h -o pch.h.pth
 +    checkstat 'cache hit (direct)' 1
 +    checkstat 'cache hit (preprocessed)' 0
 +    checkstat 'cache miss' 1
 +    if [ ! -f pch.h.pth ]; then
 +        test_failed "pch.h.pth missing"
 +    fi
 +
 +    ##################################################################
 +    # Tests for using a .pth.
 +
 +    backdate pch.h.pth
 +
 +    testname="pth, no -fpch-preprocess, -include, no sloppy time macros"
 +    $CCACHE -Cz >/dev/null
 +    $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c
 +    checkstat 'cache hit (direct)' 0
 +    checkstat 'cache hit (preprocessed)' 0
 +    checkstat 'cache miss' 0
 +    # Must enable sloppy time macros:
 +    checkstat "can't use precompiled header" 1
 +
 +    testname="pth, no -fpch-preprocess, -include, sloppy time macros"
 +    $CCACHE -Cz >/dev/null
 +    CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
 +    checkstat 'cache hit (direct)' 0
 +    checkstat 'cache hit (preprocessed)' 0
 +    checkstat 'cache miss' 1
 +    CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
 +    checkstat 'cache hit (direct)' 1
 +    checkstat 'cache hit (preprocessed)' 0
 +    checkstat 'cache miss' 1
 +
 +    testname="pth, -fpch-preprocess, -include, file changed"
 +    echo "updated" >>pch.h.pth # clang seems to cope with this...
 +    backdate pch.h.pth
 +    CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
 +    checkstat 'cache hit (direct)' 1
 +    checkstat 'cache hit (preprocessed)' 0
 +    checkstat 'cache miss' 2
 +
 +    testname="pth, preprocessor mode"
 +    $CCACHE -Cz >/dev/null
 +    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
 +    checkstat 'cache hit (direct)' 0
 +    checkstat 'cache hit (preprocessed)' 0
 +    checkstat 'cache miss' 1
 +    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
 +    checkstat 'cache hit (direct)' 0
 +    checkstat 'cache hit (preprocessed)' 1
 +    checkstat 'cache miss' 1
 +
 +    testname="pth, preprocessor mode, file changed"
 +    echo "updated" >>pch.h.pth # clang seems to cope with this...
 +    backdate pch.h.pth
 +    CCACHE_NODIRECT=1 CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
      checkstat 'cache hit (direct)' 0
      checkstat 'cache hit (preprocessed)' 1
      checkstat 'cache miss' 2
index c06a66a8326424bbb03f192910a93e5c9d317e7e,0000000000000000000000000000000000000000..30bfe432837bc10c30702aa743625ccabacfccd0
mode 100644,000000..100644
--- /dev/null
@@@ -1,420 -1,0 +1,420 @@@
-  * Copyright (C) 2011-2013 Joel Rosdahl
 +/*
-               "sloppiness =     file_macro   ,time_macros,  include_file_mtime,include_file_ctime,file_stat_matches  \n"
++ * Copyright (C) 2011-2014 Joel Rosdahl
 + *
 + * This program is free software; you can redistribute it and/or modify it
 + * under the terms of the GNU General Public License as published by the Free
 + * Software Foundation; either version 3 of the License, or (at your option)
 + * any later version.
 + *
 + * This program is distributed in the hope that it will be useful, but WITHOUT
 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 + * more details.
 + *
 + * You should have received a copy of the GNU General Public License along with
 + * this program; if not, write to the Free Software Foundation, Inc., 51
 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 + */
 +
 +#include "conf.h"
 +#include "test/framework.h"
 +#include "test/util.h"
 +
 +#define N_CONFIG_ITEMS 26
 +static struct {
 +      char *descr;
 +      const char *origin;
 +} received_conf_items[N_CONFIG_ITEMS];
 +static size_t n_received_conf_items = 0;
 +
 +static void
 +conf_item_receiver(const char *descr, const char *origin, void *context)
 +{
 +      (void)context;
 +      received_conf_items[n_received_conf_items].descr = x_strdup(descr);
 +      received_conf_items[n_received_conf_items].origin = origin;
 +      ++n_received_conf_items;
 +}
 +
 +static void
 +free_received_conf_items(void)
 +{
 +      while (n_received_conf_items > 0) {
 +              --n_received_conf_items;
 +              free(received_conf_items[n_received_conf_items].descr);
 +      }
 +}
 +
 +TEST_SUITE(conf)
 +
 +TEST(conf_create)
 +{
 +      struct conf *conf = conf_create();
 +      CHECK_STR_EQ("", conf->base_dir);
 +      CHECK_STR_EQ_FREE1(format("%s/.ccache", get_home_directory()),
 +                         conf->cache_dir);
 +      CHECK_INT_EQ(2, conf->cache_dir_levels);
 +      CHECK_STR_EQ("", conf->compiler);
 +      CHECK_STR_EQ("mtime", conf->compiler_check);
 +      CHECK(!conf->compression);
 +      CHECK_INT_EQ(6, conf->compression_level);
 +      CHECK_STR_EQ("", conf->cpp_extension);
 +      CHECK(conf->direct_mode);
 +      CHECK(!conf->disable);
 +      CHECK_STR_EQ("", conf->extra_files_to_hash);
 +      CHECK(!conf->hard_link);
 +      CHECK(!conf->hash_dir);
 +      CHECK_STR_EQ("", conf->log_file);
 +      CHECK_INT_EQ(0, conf->max_files);
 +      CHECK_INT_EQ((uint64_t)5 * 1000 * 1000 * 1000, conf->max_size);
 +      CHECK_STR_EQ("", conf->path);
 +      CHECK_STR_EQ("", conf->prefix_command);
 +      CHECK(!conf->read_only);
 +      CHECK(!conf->recache);
 +      CHECK(!conf->run_second_cpp);
 +      CHECK_INT_EQ(0, conf->sloppiness);
 +      CHECK(conf->stats);
 +      CHECK_STR_EQ("", conf->temporary_dir);
 +      CHECK_INT_EQ(UINT_MAX, conf->umask);
 +      CHECK(!conf->unify);
 +      conf_free(conf);
 +}
 +
 +TEST(conf_read_valid_config)
 +{
 +      struct conf *conf = conf_create();
 +      char *errmsg, *user;
 +      putenv("USER=rabbit");
 +      user = getenv("USER");
 +      CHECK_STR_EQ("rabbit", user);
 +      create_file(
 +              "ccache.conf",
 +              "base_dir =  /$USER/foo/${USER} \n"
 +              "cache_dir=\n"
 +              "cache_dir = $USER$/${USER}/.ccache\n"
 +              "\n"
 +              "\n"
 +              "  #A comment\n"
 +              " cache_dir_levels = 4\n"
 +              "\t compiler = foo\n"
 +              "compiler_check = none\n"
 +              "compression=true\n"
 +              "compression_level= 2\n"
 +              "cpp_extension = .foo\n"
 +              "direct_mode = false\n"
 +              "disable = true\n"
 +              "extra_files_to_hash = a:b c:$USER\n"
 +              "hard_link = true\n"
 +              "hash_dir = true\n"
 +              "log_file = $USER${USER} \n"
 +              "max_files = 17\n"
 +              "max_size = 123M\n"
 +              "path = $USER.x\n"
 +              "prefix_command = x$USER\n"
 +              "read_only = true\n"
 +              "recache = true\n"
 +              "run_second_cpp = true\n"
-                    SLOPPY_FILE_STAT_MATCHES,
++              "sloppiness =     file_macro   ,time_macros,  include_file_mtime,include_file_ctime,file_stat_matches  pch_defines  \n"
 +              "stats = false\n"
 +              "temporary_dir = ${USER}_foo\n"
 +              "umask = 777\n"
 +              "unify = true"); /* Note: no newline */
 +      CHECK(conf_read(conf, "ccache.conf", &errmsg));
 +      CHECK(!errmsg);
 +
 +      CHECK_STR_EQ_FREE1(format("/%s/foo/%s", user, user), conf->base_dir);
 +      CHECK_STR_EQ_FREE1(format("%s$/%s/.ccache", user, user), conf->cache_dir);
 +      CHECK_INT_EQ(4, conf->cache_dir_levels);
 +      CHECK_STR_EQ("foo", conf->compiler);
 +      CHECK_STR_EQ("none", conf->compiler_check);
 +      CHECK(conf->compression);
 +      CHECK_INT_EQ(2, conf->compression_level);
 +      CHECK_STR_EQ(".foo", conf->cpp_extension);
 +      CHECK(!conf->direct_mode);
 +      CHECK(conf->disable);
 +      CHECK_STR_EQ_FREE1(format("a:b c:%s", user), conf->extra_files_to_hash);
 +      CHECK(conf->hard_link);
 +      CHECK(conf->hash_dir);
 +      CHECK_STR_EQ_FREE1(format("%s%s", user, user), conf->log_file);
 +      CHECK_INT_EQ(17, conf->max_files);
 +      CHECK_INT_EQ(123 * 1000 * 1000, conf->max_size);
 +      CHECK_STR_EQ_FREE1(format("%s.x", user), conf->path);
 +      CHECK_STR_EQ_FREE1(format("x%s", user), conf->prefix_command);
 +      CHECK(conf->read_only);
 +      CHECK(conf->recache);
 +      CHECK(conf->run_second_cpp);
 +      CHECK_INT_EQ(SLOPPY_INCLUDE_FILE_MTIME|SLOPPY_INCLUDE_FILE_CTIME|
 +                   SLOPPY_FILE_MACRO|SLOPPY_TIME_MACROS|
++                   SLOPPY_FILE_STAT_MATCHES|SLOPPY_PCH_DEFINES,
 +                   conf->sloppiness);
 +      CHECK(!conf->stats);
 +      CHECK_STR_EQ_FREE1(format("%s_foo", user), conf->temporary_dir);
 +      CHECK_INT_EQ(0777, conf->umask);
 +      CHECK(conf->unify);
 +
 +      conf_free(conf);
 +}
 +
 +TEST(conf_read_with_missing_equal_sign)
 +{
 +      struct conf *conf = conf_create();
 +      char *errmsg;
 +      create_file("ccache.conf", "no equal sign");
 +      CHECK(!conf_read(conf, "ccache.conf", &errmsg));
 +      CHECK_STR_EQ_FREE2("ccache.conf:1: missing equal sign",
 +                         errmsg);
 +      conf_free(conf);
 +}
 +
 +TEST(conf_read_with_bad_config_key)
 +{
 +      struct conf *conf = conf_create();
 +      char *errmsg;
 +      create_file("ccache.conf", "# Comment\nfoo = bar");
 +      CHECK(!conf_read(conf, "ccache.conf", &errmsg));
 +      CHECK_STR_EQ_FREE2("ccache.conf:2: unknown configuration option \"foo\"",
 +                         errmsg);
 +      conf_free(conf);
 +}
 +
 +TEST(conf_read_invalid_bool)
 +{
 +      struct conf *conf = conf_create();
 +      char *errmsg;
 +
 +      create_file("ccache.conf", "disable=");
 +      CHECK(!conf_read(conf, "ccache.conf", &errmsg));
 +      CHECK_STR_EQ_FREE2("ccache.conf:1: not a boolean value: \"\"",
 +                         errmsg);
 +
 +      create_file("ccache.conf", "disable=foo");
 +      CHECK(!conf_read(conf, "ccache.conf", &errmsg));
 +      CHECK_STR_EQ_FREE2("ccache.conf:1: not a boolean value: \"foo\"",
 +                         errmsg);
 +      conf_free(conf);
 +}
 +
 +TEST(conf_read_invalid_env_string)
 +{
 +      struct conf *conf = conf_create();
 +      char *errmsg;
 +      create_file("ccache.conf", "base_dir = ${foo");
 +      CHECK(!conf_read(conf, "ccache.conf", &errmsg));
 +      CHECK_STR_EQ_FREE2("ccache.conf:1: syntax error: missing '}' after \"foo\"",
 +                         errmsg);
 +      /* Other cases tested in test_util.c. */
 +      conf_free(conf);
 +}
 +
 +TEST(conf_read_empty_umask)
 +{
 +      struct conf *conf = conf_create();
 +      char *errmsg;
 +      create_file("ccache.conf", "umask = ");
 +      CHECK(conf_read(conf, "ccache.conf", &errmsg));
 +      CHECK_INT_EQ(conf->umask, UINT_MAX);
 +      conf_free(conf);
 +}
 +
 +TEST(conf_read_invalid_size)
 +{
 +      struct conf *conf = conf_create();
 +      char *errmsg;
 +      create_file("ccache.conf", "max_size = foo");
 +      CHECK(!conf_read(conf, "ccache.conf", &errmsg));
 +      CHECK_STR_EQ_FREE2("ccache.conf:1: invalid size: \"foo\"",
 +                         errmsg);
 +      /* Other cases tested in test_util.c. */
 +      conf_free(conf);
 +}
 +
 +TEST(conf_read_invalid_sloppiness)
 +{
 +      struct conf *conf = conf_create();
 +      char *errmsg;
 +      create_file("ccache.conf", "sloppiness = file_macro, foo");
 +      CHECK(!conf_read(conf, "ccache.conf", &errmsg));
 +      CHECK_STR_EQ_FREE2("ccache.conf:1: unknown sloppiness: \"foo\"",
 +                         errmsg);
 +      conf_free(conf);
 +}
 +
 +TEST(conf_read_invalid_unsigned)
 +{
 +      struct conf *conf = conf_create();
 +      char *errmsg;
 +
 +      create_file("ccache.conf", "max_files =");
 +      CHECK(!conf_read(conf, "ccache.conf", &errmsg));
 +      CHECK_STR_EQ_FREE2("ccache.conf:1: invalid unsigned integer: \"\"",
 +                         errmsg);
 +
 +      create_file("ccache.conf", "max_files = -42");
 +      CHECK(!conf_read(conf, "ccache.conf", &errmsg));
 +      CHECK_STR_EQ_FREE2("ccache.conf:1: invalid unsigned integer: \"-42\"",
 +                         errmsg);
 +
 +      create_file("ccache.conf", "max_files = foo");
 +      CHECK(!conf_read(conf, "ccache.conf", &errmsg));
 +      CHECK_STR_EQ_FREE2("ccache.conf:1: invalid unsigned integer: \"foo\"",
 +                         errmsg);
 +
 +      conf_free(conf);
 +}
 +
 +TEST(verify_absolute_base_dir)
 +{
 +      struct conf *conf = conf_create();
 +      char *errmsg;
 +
 +      create_file("ccache.conf", "base_dir = relative/path");
 +      CHECK(!conf_read(conf, "ccache.conf", &errmsg));
 +      CHECK_STR_EQ_FREE2("ccache.conf:1: not an absolute path: \"relative/path\"",
 +                         errmsg);
 +
 +      create_file("ccache.conf", "base_dir =");
 +      CHECK(conf_read(conf, "ccache.conf", &errmsg));
 +
 +      conf_free(conf);
 +}
 +
 +TEST(verify_dir_levels)
 +{
 +      struct conf *conf = conf_create();
 +      char *errmsg;
 +
 +      create_file("ccache.conf", "cache_dir_levels = 0");
 +      CHECK(!conf_read(conf, "ccache.conf", &errmsg));
 +      CHECK_STR_EQ_FREE2("ccache.conf:1: cache directory levels must be between 1 and 8",
 +                         errmsg);
 +      create_file("ccache.conf", "cache_dir_levels = 9");
 +      CHECK(!conf_read(conf, "ccache.conf", &errmsg));
 +      CHECK_STR_EQ_FREE2("ccache.conf:1: cache directory levels must be between 1 and 8",
 +                         errmsg);
 +
 +      conf_free(conf);
 +}
 +
 +TEST(conf_update_from_environment)
 +{
 +      struct conf *conf = conf_create();
 +      char *errmsg;
 +
 +      putenv("CCACHE_COMPRESS=1");
 +      CHECK(conf_update_from_environment(conf, &errmsg));
 +      CHECK(conf->compression);
 +
 +      x_unsetenv("CCACHE_COMPRESS");
 +      putenv("CCACHE_NOCOMPRESS=1");
 +      CHECK(conf_update_from_environment(conf, &errmsg));
 +      CHECK(!conf->compression);
 +
 +      conf_free(conf);
 +}
 +
 +TEST(conf_set_new_value)
 +{
 +      char *errmsg;
 +      char *data;
 +
 +      create_file("ccache.conf", "path = vanilla\n");
 +      CHECK(conf_set_value_in_file("ccache.conf", "stats", "chocolate", &errmsg));
 +      data = read_text_file("ccache.conf", 0);
 +      CHECK(data);
 +      CHECK_STR_EQ_FREE2("path = vanilla\nstats = chocolate\n", data);
 +}
 +
 +TEST(conf_set_existing_value)
 +{
 +      char *errmsg;
 +      char *data;
 +
 +      create_file("ccache.conf", "path = chocolate\nstats = chocolate\n");
 +      CHECK(conf_set_value_in_file("ccache.conf", "path", "vanilla", &errmsg));
 +      data = read_text_file("ccache.conf", 0);
 +      CHECK(data);
 +      CHECK_STR_EQ_FREE2("path = vanilla\nstats = chocolate\n", data);
 +}
 +
 +TEST(conf_print_items)
 +{
 +      size_t i;
 +      struct conf conf = {
 +              "bd",
 +              "cd",
 +              7,
 +              "c",
 +              "cc",
 +              true,
 +              8,
 +              "ce",
 +              false,
 +              true,
 +              "efth",
 +              true,
 +              true,
 +              "lf",
 +              4711,
 +              98.7 * 1000 * 1000,
 +              "p",
 +              "pc",
 +              true,
 +              true,
 +              true,
 +              SLOPPY_FILE_MACRO|SLOPPY_INCLUDE_FILE_MTIME|
 +                SLOPPY_INCLUDE_FILE_CTIME|SLOPPY_TIME_MACROS|
 +                SLOPPY_FILE_STAT_MATCHES,
 +              false,
 +              "td",
 +              022,
 +              true,
 +              NULL
 +      };
 +      size_t n = 0;
 +
 +      conf.item_origins = x_malloc(N_CONFIG_ITEMS * sizeof(char *));
 +      for (i = 0; i < N_CONFIG_ITEMS; ++i) {
 +              conf.item_origins[i] = format("origin%zu", i);
 +      }
 +
 +      conf_print_items(&conf, conf_item_receiver, NULL);
 +      CHECK_INT_EQ(N_CONFIG_ITEMS, n_received_conf_items);
 +      CHECK_STR_EQ("base_dir = bd", received_conf_items[n++].descr);
 +      CHECK_STR_EQ("cache_dir = cd", received_conf_items[n++].descr);
 +      CHECK_STR_EQ("cache_dir_levels = 7", received_conf_items[n++].descr);
 +      CHECK_STR_EQ("compiler = c", received_conf_items[n++].descr);
 +      CHECK_STR_EQ("compiler_check = cc", received_conf_items[n++].descr);
 +      CHECK_STR_EQ("compression = true", received_conf_items[n++].descr);
 +      CHECK_STR_EQ("compression_level = 8", received_conf_items[n++].descr);
 +      CHECK_STR_EQ("cpp_extension = ce", received_conf_items[n++].descr);
 +      CHECK_STR_EQ("direct_mode = false", received_conf_items[n++].descr);
 +      CHECK_STR_EQ("disable = true", received_conf_items[n++].descr);
 +      CHECK_STR_EQ("extra_files_to_hash = efth", received_conf_items[n++].descr);
 +      CHECK_STR_EQ("hard_link = true", received_conf_items[n++].descr);
 +      CHECK_STR_EQ("hash_dir = true", received_conf_items[n++].descr);
 +      CHECK_STR_EQ("log_file = lf", received_conf_items[n++].descr);
 +      CHECK_STR_EQ("max_files = 4711", received_conf_items[n++].descr);
 +      CHECK_STR_EQ("max_size = 98.7M", received_conf_items[n++].descr);
 +      CHECK_STR_EQ("path = p", received_conf_items[n++].descr);
 +      CHECK_STR_EQ("prefix_command = pc", received_conf_items[n++].descr);
 +      CHECK_STR_EQ("read_only = true", received_conf_items[n++].descr);
 +      CHECK_STR_EQ("recache = true", received_conf_items[n++].descr);
 +      CHECK_STR_EQ("run_second_cpp = true", received_conf_items[n++].descr);
 +      CHECK_STR_EQ("sloppiness = file_macro, include_file_mtime,"
 +                   " include_file_ctime, time_macros, file_stat_matches",
 +                   received_conf_items[n++].descr);
 +      CHECK_STR_EQ("stats = false", received_conf_items[n++].descr);
 +      CHECK_STR_EQ("temporary_dir = td", received_conf_items[n++].descr);
 +      CHECK_STR_EQ("umask = 022", received_conf_items[n++].descr);
 +      CHECK_STR_EQ("unify = true", received_conf_items[n++].descr);
 +
 +      for (i = 0; i < N_CONFIG_ITEMS; ++i) {
 +              char *expected = format("origin%zu", i);
 +              CHECK_STR_EQ(expected, received_conf_items[i].origin);
 +      }
 +
 +      free_received_conf_items();
 +      free(conf.item_origins);
 +}
 +
 +TEST_SUITE_END