]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
Merge branch 'master' into config
authorJoel Rosdahl <joel@rosdahl.net>
Mon, 20 Feb 2012 22:16:25 +0000 (23:16 +0100)
committerJoel Rosdahl <joel@rosdahl.net>
Mon, 20 Feb 2012 22:16:25 +0000 (23:16 +0100)
* master:
  Minor code style changes
  Support adjusting the compression level through CCACHE_COMPRESS_LEVEL

Conflicts:
MANUAL.txt
ccache.c

1  2 
MANUAL.txt
ccache.c
ccache.h
conf.c
conf.h
confitems.gperf
confitems_lookup.c
envtoconfitems.gperf
envtoconfitems_lookup.c
test/test_conf.c
util.c

diff --cc MANUAL.txt
index b4679766ba94ec2c7c2db2797a189299e288e67c,b263c5a788405c55fb19434ee950ac8a386d5ba6..6717fdebc3704067e29a641e5e604e7fd5a2f312
@@@ -307,105 -236,136 +307,112 @@@ WRAPPERS>>
  --
  --
  
 -*CCACHE_COMPRESS*::
 -
 -    If you set the environment variable *CCACHE_COMPRESS* then ccache will
 -    compress object files and other compiler output it puts in the cache.
 -    However, this setting has no effect on how files are retrieved from the
 -    cache; compressed and uncompressed results will still be usable regardless
 -    of this setting.
 -
 -*CCACHE_COMPRESS_LEVEL*::
 -
 -    This environment variable determines the level at which ccache will
 -    compress object files. It only has effect if *CCACHE_COMPRESS* is also
 -    set. The value defaults to 6, and must be no lower than 1 (fastest, worst
 -    compression) and no higher than 9 (slowest, best compression).
 -
 -*CCACHE_CPP2*::
 -
 -    If you set the environment variable *CCACHE_CPP2* then ccache will not use
 -    the optimisation of avoiding the second call to the preprocessor by
 -    compiling the preprocessed output that was used for finding the hash in the
 -    case of a cache miss. This is primarily a debugging option, although it is
 -    possible that some unusual compilers will have problems with the
 -    intermediate filename extensions used in this optimisation, in which case
 -    this option could allow ccache to be used anyway.
 -
 -*CCACHE_DETECT_SHEBANG*::
 +*compression* (*CCACHE_COMPRESS*) [boolean]::
  
 -    The *CCACHE_DETECT_SHEBANG* environment variable only has meaning on
 -    Windows. It instructs ccache to open the executable file to detect the
 -    *#!/bin/sh* string, in which case ccache will search for *sh.exe* in
 -    *PATH* and use that to launch the executable.
 +    If true, ccache will compress object files and other compiler output it
 +    puts in the cache. However, this setting has no effect on how files are
 +    retrieved from the cache; compressed and uncompressed results will still be
 +    usable regardless of this setting. The default is false.
  
 -*CCACHE_DIR*::
++*compression_level* (*CCACHE_COMPRESSLEVEL*)::
 -    The *CCACHE_DIR* environment variable specifies where ccache will keep its
 -    cached compiler output. The default is *$HOME/.ccache*.
++    This setting determines the level at which ccache will compress object
++    files. It only has effect if *compression* is enabled. The value defaults
++    to 6, and must be no lower than 1 (fastest, worst compression) and no
++    higher than 9 (slowest, best compression).
 -*CCACHE_DISABLE*::
 +*cpp_extension* (*CCACHE_EXTENSION*)::
  
 -    If you set the environment variable *CCACHE_DISABLE* then ccache will just
 -    call the real compiler, bypassing the cache completely.
 +    This setting can be used to force a certain extension for the intermediate
 +    preprocessed file. The default is to automatically determine the extension
 +    to use for intermediate preprocessor files based on the type of file being
 +    compiled, but that sometimes doesn't work. For example, when using the
 +    ``aCC'' compiler on HP-UX, set the cpp extension to *i*.
  
 -*CCACHE_EXTENSION*::
 +*direct_mode* (*CCACHE_DIRECT*) [boolean]::
  
 -    ccache tries to automatically determine the extension to use for
 -    intermediate preprocessor files based on the type of file being compiled.
 -    Unfortunately this sometimes doesn't work, for example when using the
 -    ``aCC'' compiler on HP-UX. On systems like this you can use the
 -    *CCACHE_EXTENSION* option to override the default. On HP-UX set this
 -    environment variable to *i* if you use the ``aCC'' compiler.
 +    If true, the direct mode will be used. The default is true.
  
 -*CCACHE_EXTRAFILES*::
 +*disable* (*CCACHE_DISABLE*) [boolean]::
  
 -    If you set the environment variable *CCACHE_EXTRAFILES* to a list of paths
 -    then ccache will include the contents of those files when calculating the
 -    hash sum. The list separator is semicolon in Windows systems and colon on
 -    other systems.
 +    When true, ccache will just call the real compiler, bypassing the cache
 +    completely. The default is false.
  
 -*CCACHE_HARDLINK*::
 +*extra_files_to_hash* (*CCACHE_EXTRAFILES*)::
  
 -    If you set the environment variable *CCACHE_HARDLINK* then ccache will
 -    attempt to use hard links from the cache directory when creating the
 -    compiler output rather than using a file copy. Using hard links may be
 -    slightly faster in some situations, but can confuse programs like ``make''
 -    that rely on modification times. Another thing to keep in mind is that if
 -    the resulting object file is modified in any way, this corrupts the cached
 -    object file as well. Hard links are never made for compressed cache files.
 -    This means that you should not set the *CCACHE_COMPRESS* variable if you
 -    want to use hard links.
 +    This setting is a list of paths to files that ccache will include in the
 +    the hash sum that idetifies the build. The list separator is semicolon on
 +    Windows systems and colon on other systems.
  
 -*CCACHE_HASHDIR*::
 +*hard_link* (*CCACHE_HARDLINK*) [boolean]::
  
 -    This tells ccache to hash the current working directory when calculating
 -    the hash that is used to distinguish two compilations. This prevents a
 -    problem with the storage of the current working directory in the debug info
 -    of a object file, which can lead ccache to give a cached object file that
 -    has the working directory in the debug info set incorrectly. This option is
 -    off by default as the incorrect setting of this debug info rarely causes
 -    problems. If you strike problems with GDB not using the correct directory
 -    then enable this option.
 +    If true, ccache will attempt to use hard links from the cache directory
 +    when creating the compiler output rather than using a file copy. Using hard
 +    links may be slightly faster in some situations, but can confuse programs
 +    like ``make'' that rely on modification times. Another thing to keep in
 +    mind is that if the resulting object file is modified in any way, this
 +    corrupts the cached object file as well. Hard links are never made for
 +    compressed cache files. This means that you should not enable compression
 +    if you want to use hard links. The default is false.
  
 -*CCACHE_LOGFILE*::
 +*hash_dir* (*CCACHE_HASHDIR*) [boolean]::
  
 -    If you set the *CCACHE_LOGFILE* environment variable then ccache will write
 -    information on what it is doing to the specified file. This is useful for
 -    tracking down problems.
 +    If true, ccache will include the current working directory in the hash that
 +    is used to distinguish two compilations. This prevents a problem with the
 +    storage of the current working directory in the debug info of a object
 +    file, which can lead ccache to give a cached object file that has the
 +    working directory in the debug info set incorrectly. This option is off by
 +    default as the incorrect setting of this debug info rarely causes problems.
 +    If you strike problems with GDB not using the correct directory then enable
 +    this option.
  
 -*CCACHE_NLEVELS*::
 +*log_file* (*CCACHE_LOGFILE*)::
  
 -    The environment variable *CCACHE_NLEVELS* allows you to choose the number
 -    of levels of hash in the cache directory. The default is 2. The minimum is
 -    1 and the maximum is 8.
 +    If set to a file path, ccache will write information on what it is doing to
 +    the specified file. This is useful for tracking down problems.
  
 -*CCACHE_NODIRECT*::
 +*stats* (*CCACHE_STATS*) [boolean]::
  
 -    If you set the environment variable *CCACHE_NODIRECT* then ccache will not
 -    use the direct mode.
 +    If true, ccache will update the statistics counters on each compilation.
 +    The default is true.
  
 -*CCACHE_NOSTATS*::
 +*path* (*CCACHE_PATH*)::
  
 -    If you set the environment variable *CCACHE_NOSTATS* then ccache will not
 -    update the statistics files on each compilation.
 +    If set, ccache will search directories in this list when looking for the
 +    real compiler. The list separator is semicolon on Windows systems and colon
 +    on other systems. If not set, ccache will look for the first executable
 +    matching the compiler name in the normal *PATH* that isn't a symbolic link
 +    to ccache itself.
  
 -*CCACHE_PATH*::
 +*prefix_command* (*CCACHE_PREFIX*)::
  
 -    You can optionally set *CCACHE_PATH* to a colon-separated path where ccache
 -    will look for the real compilers. If you don't do this then ccache will
 -    look for the first executable matching the compiler name in the normal
 -    *PATH* that isn't a symbolic link to ccache itself.
 +    This option adds a list of prefixes (separated by space) to the command
 +    line that ccache uses when invoking the compiler. See also
 +    <<_using_ccache_with_other_compiler_wrappers,USING CCACHE WITH OTHER
 +    COMPILER WRAPPERS>>.
  
 -*CCACHE_PREFIX*::
 +*read_only* (*CCACHE_READONLY*) [boolean]::
  
 -    This option adds a prefix to the command line that ccache runs when
 -    invoking the compiler. Also see the section below on using ccache with
 -    ``distcc''.
 +    If true, ccache will attempt to use existing cached object files, but it
 +    will not to try to add anything new to the cache. If you are using this
 +    because your ccache directory is read-only, then you need to set
 +    *temporary_dir* as otherwise ccache will fail to create temporary files.
  
 -*CCACHE_READONLY*::
 +*reache* (*CCACHE_RECACHE*) [boolean]::
  
 -    The *CCACHE_READONLY* environment variable tells ccache to attempt to use
 -    existing cached object files, but not to try to add anything new to the
 -    cache. If you are using this because your *CCACHE_DIR* is read-only, then
 -    you may find that you also need to set *CCACHE_TEMPDIR* as otherwise ccache
 -    will fail to create temporary files.
 +    If true, ccache will not use any previously stored result. New results will
 +    still be cached, possibly overwriting any pre-existing results.
  
 -*CCACHE_RECACHE*::
 +*run_second_cpp* (*CCACHE_CPP2*) [boolean]::
  
 -    This forces ccache to not use any cached results, even if it finds them.
 -    New results are still cached, but existing cache entries are ignored.
 +    If true, ccache will not use the optimisation of avoiding the second call
 +    to the preprocessor by compiling the preprocessed output that was used for
 +    finding the hash in the case of a cache miss. This is primarily a debugging
 +    option, although it is possible that some unusual compilers will have
 +    problems with compiling the preprocessed output, in which case this option
 +    could allow ccache to be used anyway.
  
 -*CCACHE_SLOPPINESS*::
 +*sloppiness* (*CCACHE_SLOPPINESS*)::
  
      By default, ccache tries to give as few false cache hits as possible.
      However, in certain situations it's possible that you know things that
@@@ -469,9 -431,10 +476,10 @@@ Cache compressio
  -----------------
  
  ccache can optionally compress all files it puts into the cache using the
 -compression library zlib. While this involves a negligible performance
 -slowdown, it significantly increases the number of files that fit in the cache.
 -You can turn on compression by setting the *CCACHE_COMPRESS* environment
 -variable.
 +compression library zlib. While this may involve a tiny performance slowdown,
 +it increases the number of files that fit in the cache. You can turn on
- compression with the *compress* configuration setting.
++compression with the *compression* configuration setting and you can also tweak
++the compression level with *compression_level*.
  
  
  How ccache works
diff --cc ccache.c
index 5a55e08ff9a69b618b5bfcd6e46485358a228ec3,7942104734d3fbeecdc46b7c1fda9ba60b51cb62..44046efc52163d4d7fde908f06e2c34793797251
+++ b/ccache.c
@@@ -675,8 -698,8 +675,9 @@@ to_cache(struct args *args
                failed();
        }
        if (st.st_size > 0) {
--              if (move_uncompressed_file(tmp_stderr, cached_stderr,
-                                          conf->compression) != 0) {
 -                                         enable_compression) != 0) {
++              if (move_uncompressed_file(
++                          tmp_stderr, cached_stderr,
++                          conf->compression ? conf->compression_level : 0) != 0) {
                        cc_log("Failed to move %s to %s: %s", tmp_stderr, cached_stderr,
                               strerror(errno));
                        stats_update(STATS_ERROR);
                        stats_update(STATS_ERROR);
                        failed();
                }
-       } else if (move_uncompressed_file(tmp_obj, cached_obj, conf->compression) != 0) {
 -      } else if (move_uncompressed_file(tmp_obj, cached_obj, enable_compression) != 0) {
++      } else if (move_uncompressed_file(
++                         tmp_obj, cached_obj,
++                         conf->compression ? conf->compression_level : 0) != 0) {
                cc_log("Failed to move %s to %s: %s", tmp_obj, cached_obj, strerror(errno));
                stats_update(STATS_ERROR);
                failed();
diff --cc ccache.h
Simple merge
diff --cc conf.c
index de549e1500345270d0066cde968201c0f1667280,0000000000000000000000000000000000000000..ea052db577f53bcc4632dd471385d7cadc22caee
mode 100644,000000..100644
--- 1/conf.c
--- /dev/null
+++ b/conf.c
@@@ -1,610 -1,0 +1,615 @@@
 +/*
 + * Copyright (C) 2011-2012 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, "include_file_mtime")) {
 +                      *value |= SLOPPY_INCLUDE_FILE_MTIME;
 +              } 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 && x <= (unsigned)-1 && *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 *)((void *)conf + item->offset);
 +              *value = !negate_boolean;
 +              goto out;
 +      }
 +
 +      if (!item->parser(value, (void *)conf + item->offset, errmsg)) {
 +              return false;
 +      }
 +      if (item->verifier && !item->verifier((void *)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_TIME_MACROS) {
 +              reformat(&s, "%stime_macros, ", 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 conf.h
index e95a26869eb22cc6d2903fa814e9b1dc95eb301b,0000000000000000000000000000000000000000..9963bced07487f8e9a81493ad7ca634fbce1c7fb
mode 100644,000000..100644
--- 1/conf.h
--- /dev/null
+++ b/conf.h
@@@ -1,47 -1,0 +1,48 @@@
 +#ifndef CONF_H
 +#define CONF_H
 +
 +#include "system.h"
 +
 +struct conf {
 +      char *base_dir;
 +      char *cache_dir;
 +      unsigned cache_dir_levels;
 +      char *compiler;
 +      char *compiler_check;
 +      bool compression;
++      unsigned compression_level;
 +      char *cpp_extension;
 +      bool direct_mode;
 +      bool disable;
 +      char *extra_files_to_hash;
 +      bool hard_link;
 +      bool hash_dir;
 +      char *log_file;
 +      unsigned max_files;
 +      uint64_t max_size;
 +      char *path;
 +      char *prefix_command;
 +      bool read_only;
 +      bool recache;
 +      bool run_second_cpp;
 +      unsigned sloppiness;
 +      bool stats;
 +      char *temporary_dir;
 +      unsigned umask;
 +      bool unify;
 +
 +      const char **item_origins;
 +};
 +
 +struct conf *conf_create(void);
 +void conf_free(struct conf *conf);
 +bool conf_read(struct conf *conf, const char *path, char **errmsg);
 +bool conf_update_from_environment(struct conf *conf, char **errmsg);
 +bool conf_set_value_in_file(const char *path, const char *key,
 +                            const char *value, char **errmsg);
 +bool conf_print_items(struct conf *conf,
 +                      void (*printer)(const char *descr, const char *origin,
 +                                      void *context),
 +                      void *context);
 +
 +#endif
diff --cc confitems.gperf
index 75c6765ad002bda85f8b73c278ffee683ec6057f,0000000000000000000000000000000000000000..ae48d2b4b9724de975eb22fbc03d57182410481e
mode 100644,000000..100644
--- /dev/null
@@@ -1,34 -1,0 +1,35 @@@
- cpp_extension,        6, ITEM(cpp_extension, string)
- direct_mode,          7, ITEM(direct_mode, bool)
- disable,              8, ITEM(disable, bool)
- extra_files_to_hash,  9, ITEM(extra_files_to_hash, env_string)
- hard_link,           10, ITEM(hard_link, bool)
- hash_dir,            11, ITEM(hash_dir, bool)
- log_file,            12, ITEM(log_file, env_string)
- max_files,           13, ITEM(max_files, unsigned)
- max_size,            14, ITEM(max_size, size)
- path,                15, ITEM(path, env_string)
- prefix_command,      16, ITEM(prefix_command, env_string)
- read_only,           17, ITEM(read_only, bool)
- recache,             18, ITEM(recache, bool)
- run_second_cpp,      19, ITEM(run_second_cpp, bool)
- sloppiness,          20, ITEM(sloppiness, sloppiness)
- stats,               21, ITEM(stats, bool)
- temporary_dir,       22, ITEM(temporary_dir, env_string)
- umask,               23, ITEM(umask, umask)
- unify,               24, ITEM(unify, bool)
 +%language=ANSI-C
 +%enum
 +%struct-type
 +%readonly-tables
 +%define hash-function-name confitems_hash
 +%define lookup-function-name confitems_get
 +%define initializer-suffix ,0,NULL,0,NULL
 +struct conf_item;
 +%%
 +base_dir,             0, ITEM_V(base_dir, env_string, absolute_path)
 +cache_dir,            1, ITEM(cache_dir, env_string)
 +cache_dir_levels,     2, ITEM_V(cache_dir_levels, unsigned, dir_levels)
 +compiler,             3, ITEM(compiler, string)
 +compiler_check,       4, ITEM(compiler_check, string)
 +compression,          5, ITEM(compression, bool)
++compression_level,    6, ITEM(compression_level, unsigned)
++cpp_extension,        7, ITEM(cpp_extension, string)
++direct_mode,          8, ITEM(direct_mode, bool)
++disable,              9, ITEM(disable, bool)
++extra_files_to_hash, 10, ITEM(extra_files_to_hash, env_string)
++hard_link,           11, ITEM(hard_link, bool)
++hash_dir,            12, ITEM(hash_dir, bool)
++log_file,            13, ITEM(log_file, env_string)
++max_files,           14, ITEM(max_files, unsigned)
++max_size,            15, ITEM(max_size, size)
++path,                16, ITEM(path, env_string)
++prefix_command,      17, ITEM(prefix_command, env_string)
++read_only,           18, ITEM(read_only, bool)
++recache,             19, ITEM(recache, bool)
++run_second_cpp,      20, ITEM(run_second_cpp, bool)
++sloppiness,          21, ITEM(sloppiness, sloppiness)
++stats,               22, ITEM(stats, bool)
++temporary_dir,       23, ITEM(temporary_dir, env_string)
++umask,               24, ITEM(umask, umask)
++unify,               25, ITEM(unify, bool)
index 877a7e300bc567b92e9ac06568821bd251e31f0d,0000000000000000000000000000000000000000..e00ff9be7a85a775234ad96636930381e9b6d3b2
mode 100644,000000..100644
--- /dev/null
@@@ -1,176 -1,0 +1,178 @@@
-        5, 15, 45, 45, 30,  5, 45, 45, 15, 10,
 +/* ANSI-C code produced by gperf version 3.0.3 */
 +/* Command-line: gperf confitems.gperf  */
 +/* Computed positions: -k'1-2' */
 +
 +#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \
 +      && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \
 +      && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \
 +      && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \
 +      && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \
 +      && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \
 +      && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \
 +      && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \
 +      && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \
 +      && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \
 +      && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \
 +      && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \
 +      && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \
 +      && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \
 +      && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \
 +      && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \
 +      && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \
 +      && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \
 +      && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \
 +      && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \
 +      && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \
 +      && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \
 +      && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126))
 +/* The character set is not based on ISO-646.  */
 +#error "gperf generated tables don't work with this execution character set. Please report a bug to <bug-gnu-gperf@gnu.org>."
 +#endif
 +
 +#line 8 "confitems.gperf"
 +struct conf_item;
 +/* maximum key range = 41, duplicates = 0 */
 +
 +#ifdef __GNUC__
 +__inline
 +#else
 +#ifdef __cplusplus
 +inline
 +#endif
 +#endif
 +static unsigned int
 +confitems_hash (register const char *str, register unsigned int len)
 +{
 +  static const unsigned char asso_values[] =
 +    {
 +      45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
 +      45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
 +      45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
 +      45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
 +      45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
 +      45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
 +      45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
 +      45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
 +      45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
 +      45, 45, 45, 45, 45, 45, 45,  0, 25,  0,
-       TOTAL_KEYWORDS = 25,
++      10, 15, 45, 45, 30,  5, 45, 45, 15, 10,
 +       0,  0,  0, 45, 10,  0,  0,  5, 45, 45,
 +      10, 45, 45, 45, 45, 45, 45, 45, 45, 45,
 +      45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
 +      45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
 +      45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
 +      45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
 +      45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
 +      45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
 +      45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
 +      45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
 +      45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
 +      45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
 +      45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
 +      45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
 +      45, 45, 45, 45, 45, 45
 +    };
 +  return len + asso_values[(unsigned char)str[1]] + asso_values[(unsigned char)str[0]];
 +}
 +
 +#ifdef __GNUC__
 +__inline
 +#ifdef __GNUC_STDC_INLINE__
 +__attribute__ ((__gnu_inline__))
 +#endif
 +#endif
 +const struct conf_item *
 +confitems_get (register const char *str, register unsigned int len)
 +{
 +  enum
 +    {
- #line 25 "confitems.gperf"
-       {"path",                15, ITEM(path, env_string)},
- #line 31 "confitems.gperf"
-       {"stats",               21, ITEM(stats, bool)},
++      TOTAL_KEYWORDS = 26,
 +      MIN_WORD_LENGTH = 4,
 +      MAX_WORD_LENGTH = 19,
 +      MIN_HASH_VALUE = 4,
 +      MAX_HASH_VALUE = 44
 +    };
 +
 +  static const struct conf_item wordlist[] =
 +    {
 +      {"",0,NULL,0,NULL}, {"",0,NULL,0,NULL},
 +      {"",0,NULL,0,NULL}, {"",0,NULL,0,NULL},
- #line 34 "confitems.gperf"
-       {"unify",               24, ITEM(unify, bool)},
++#line 26 "confitems.gperf"
++      {"path",                16, ITEM(path, env_string)},
++#line 32 "confitems.gperf"
++      {"stats",               22, ITEM(stats, bool)},
 +      {"",0,NULL,0,NULL}, {"",0,NULL,0,NULL},
 +#line 13 "confitems.gperf"
 +      {"compiler",             3, ITEM(compiler, string)},
 +#line 11 "confitems.gperf"
 +      {"cache_dir",            1, ITEM(cache_dir, env_string)},
- #line 16 "confitems.gperf"
-       {"cpp_extension",        6, ITEM(cpp_extension, string)},
++#line 35 "confitems.gperf"
++      {"unify",               25, ITEM(unify, bool)},
 +#line 15 "confitems.gperf"
 +      {"compression",          5, ITEM(compression, bool)},
 +      {"",0,NULL,0,NULL},
- #line 18 "confitems.gperf"
-       {"disable",              8, ITEM(disable, bool)},
++#line 17 "confitems.gperf"
++      {"cpp_extension",        7, ITEM(cpp_extension, string)},
 +#line 14 "confitems.gperf"
 +      {"compiler_check",       4, ITEM(compiler_check, string)},
 +      {"",0,NULL,0,NULL},
 +#line 12 "confitems.gperf"
 +      {"cache_dir_levels",     2, ITEM_V(cache_dir_levels, unsigned, dir_levels)},
-       {"max_size",            14, ITEM(max_size, size)},
++#line 16 "confitems.gperf"
++      {"compression_level",    6, ITEM(compression_level, unsigned)},
++#line 25 "confitems.gperf"
++      {"max_size",            15, ITEM(max_size, size)},
 +#line 24 "confitems.gperf"
-       {"max_files",           13, ITEM(max_files, unsigned)},
- #line 33 "confitems.gperf"
-       {"umask",               23, ITEM(umask, umask)},
- #line 17 "confitems.gperf"
-       {"direct_mode",          7, ITEM(direct_mode, bool)},
++      {"max_files",           14, ITEM(max_files, unsigned)},
++#line 34 "confitems.gperf"
++      {"umask",               24, ITEM(umask, umask)},
++      {"",0,NULL,0,NULL},
++#line 19 "confitems.gperf"
++      {"disable",              9, ITEM(disable, bool)},
 +#line 23 "confitems.gperf"
- #line 22 "confitems.gperf"
-       {"log_file",            12, ITEM(log_file, env_string)},
- #line 26 "confitems.gperf"
-       {"prefix_command",      16, ITEM(prefix_command, env_string)},
++      {"log_file",            13, ITEM(log_file, env_string)},
++#line 27 "confitems.gperf"
++      {"prefix_command",      17, ITEM(prefix_command, env_string)},
++#line 31 "confitems.gperf"
++      {"sloppiness",          21, ITEM(sloppiness, sloppiness)},
++#line 18 "confitems.gperf"
++      {"direct_mode",          8, ITEM(direct_mode, bool)},
 +      {"",0,NULL,0,NULL},
-       {"sloppiness",          20, ITEM(sloppiness, sloppiness)},
++#line 33 "confitems.gperf"
++      {"temporary_dir",       23, ITEM(temporary_dir, env_string)},
 +#line 30 "confitems.gperf"
- #line 32 "confitems.gperf"
-       {"temporary_dir",       22, ITEM(temporary_dir, env_string)},
++      {"run_second_cpp",      20, ITEM(run_second_cpp, bool)},
 +      {"",0,NULL,0,NULL}, {"",0,NULL,0,NULL},
-       {"run_second_cpp",      19, ITEM(run_second_cpp, bool)},
-       {"",0,NULL,0,NULL}, {"",0,NULL,0,NULL},
- #line 28 "confitems.gperf"
-       {"recache",             18, ITEM(recache, bool)},
 +#line 29 "confitems.gperf"
- #line 27 "confitems.gperf"
-       {"read_only",           17, ITEM(read_only, bool)},
++      {"recache",             19, ITEM(recache, bool)},
 +#line 10 "confitems.gperf"
 +      {"base_dir",             0, ITEM_V(base_dir, env_string, absolute_path)},
-       {"hash_dir",            11, ITEM(hash_dir, bool)},
- #line 20 "confitems.gperf"
-       {"hard_link",           10, ITEM(hard_link, bool)},
++#line 28 "confitems.gperf"
++      {"read_only",           18, ITEM(read_only, bool)},
 +      {"",0,NULL,0,NULL}, {"",0,NULL,0,NULL},
 +      {"",0,NULL,0,NULL},
++#line 22 "confitems.gperf"
++      {"hash_dir",            12, ITEM(hash_dir, bool)},
 +#line 21 "confitems.gperf"
- #line 19 "confitems.gperf"
-       {"extra_files_to_hash",  9, ITEM(extra_files_to_hash, env_string)}
++      {"hard_link",           11, ITEM(hard_link, bool)},
 +      {"",0,NULL,0,NULL}, {"",0,NULL,0,NULL},
 +      {"",0,NULL,0,NULL}, {"",0,NULL,0,NULL},
- static const size_t CONFITEMS_TOTAL_KEYWORDS = 25;
++#line 20 "confitems.gperf"
++      {"extra_files_to_hash", 10, ITEM(extra_files_to_hash, env_string)}
 +    };
 +
 +  if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH)
 +    {
 +      register int key = confitems_hash (str, len);
 +
 +      if (key <= MAX_HASH_VALUE && key >= 0)
 +        {
 +          register const char *s = wordlist[key].name;
 +
 +          if (*str == *s && !strcmp (str + 1, s + 1))
 +            return &wordlist[key];
 +        }
 +    }
 +  return 0;
 +}
++static const size_t CONFITEMS_TOTAL_KEYWORDS = 26;
index a528af5b6c15e9cc4320717a632248300ed64073,0000000000000000000000000000000000000000..635bc562d79a7e8e595fbb14ca1bf2e3aa354bf1
mode 100644,000000..100644
--- /dev/null
@@@ -1,35 -1,0 +1,36 @@@
 +%language=ANSI-C
 +%enum
 +%struct-type
 +%readonly-tables
 +%define hash-function-name envtoconfitems_hash
 +%define lookup-function-name envtoconfitems_get
 +%define slot-name env_name
 +%define initializer-suffix ,""
 +struct env_to_conf_item;
 +%%
 +BASEDIR, "base_dir"
 +CC, "compiler"
 +COMPILERCHECK, "compiler_check"
 +COMPRESS, "compression"
++COMPRESSLEVEL, "compression_level"
 +CPP2, "run_second_cpp"
 +DIR, "cache_dir"
 +DIRECT, "direct_mode"
 +DISABLE, "disable"
 +EXTENSION, "cpp_extension"
 +EXTRAFILES, "extra_files_to_hash"
 +HARDLINK, "hard_link"
 +HASHDIR, "hash_dir"
 +LOGFILE, "log_file"
 +MAXFILES, "max_files"
 +MAXSIZE, "max_size"
 +NLEVELS, "cache_dir_levels"
 +PATH, "path"
 +PREFIX, "prefix_command"
 +READONLY, "read_only"
 +RECACHE, "recache"
 +SLOPPINESS, "sloppiness"
 +STATS, "stats"
 +TEMPDIR, "temporary_dir"
 +UMASK, "umask"
 +UNIFY, "unify"
index c21f2a267cbf2eea483342010c296c97966835da,0000000000000000000000000000000000000000..8c99738e0ee9f58c8fb03ac5e8cac1be69c00e4e
mode 100644,000000..100644
--- /dev/null
@@@ -1,176 -1,0 +1,191 @@@
- /* Computed positions: -k'1-2' */
 +/* ANSI-C code produced by gperf version 3.0.3 */
 +/* Command-line: gperf envtoconfitems.gperf  */
-       43, 43, 43, 43, 43, 10,  5,  0,  0,  0,
-       43, 43, 20,  0, 43, 43, 15, 10, 20, 10,
-        0, 43,  5,  0, 10,  5, 43, 43,  0, 43,
++/* Computed positions: -k'1,5' */
 +
 +#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \
 +      && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \
 +      && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \
 +      && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \
 +      && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \
 +      && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \
 +      && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \
 +      && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \
 +      && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \
 +      && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \
 +      && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \
 +      && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \
 +      && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \
 +      && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \
 +      && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \
 +      && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \
 +      && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \
 +      && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \
 +      && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \
 +      && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \
 +      && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \
 +      && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \
 +      && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126))
 +/* The character set is not based on ISO-646.  */
 +#error "gperf generated tables don't work with this execution character set. Please report a bug to <bug-gnu-gperf@gnu.org>."
 +#endif
 +
 +#line 9 "envtoconfitems.gperf"
 +struct env_to_conf_item;
 +/* maximum key range = 41, duplicates = 0 */
 +
 +#ifdef __GNUC__
 +__inline
 +#else
 +#ifdef __cplusplus
 +inline
 +#endif
 +#endif
 +static unsigned int
 +envtoconfitems_hash (register const char *str, register unsigned int len)
 +{
 +  static const unsigned char asso_values[] =
 +    {
 +      43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
 +      43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
 +      43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
 +      43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
 +      43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
 +      43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
-       43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
-       43, 43, 43, 43, 43, 43
++      43, 43, 43, 43, 43, 43,  5,  0,  0, 10,
++      20, 43, 15, 43, 10, 43, 20, 10, 15,  0,
++       5,  5,  5,  0,  0,  5, 43, 43, 43, 43,
++       0, 43, 43, 43, 43, 43, 43, 43, 43, 43,
 +      43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
 +      43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
 +      43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
 +      43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
 +      43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
 +      43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
 +      43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
 +      43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
 +      43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
 +      43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
 +      43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
 +      43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
 +      43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
 +      43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
 +      43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
-   return len + asso_values[(unsigned char)str[1]] + asso_values[(unsigned char)str[0]];
++      43, 43, 43, 43, 43, 43, 43
 +    };
-       TOTAL_KEYWORDS = 25,
++  register int hval = len;
++
++  switch (hval)
++    {
++      default:
++        hval += asso_values[(unsigned char)str[4]+1];
++      /*FALLTHROUGH*/
++      case 4:
++      case 3:
++      case 2:
++      case 1:
++        hval += asso_values[(unsigned char)str[0]];
++        break;
++    }
++  return hval;
 +}
 +
 +#ifdef __GNUC__
 +__inline
 +#ifdef __GNUC_STDC_INLINE__
 +__attribute__ ((__gnu_inline__))
 +#endif
 +#endif
 +const struct env_to_conf_item *
 +envtoconfitems_get (register const char *str, register unsigned int len)
 +{
 +  enum
 +    {
- #line 16 "envtoconfitems.gperf"
++      TOTAL_KEYWORDS = 26,
 +      MIN_WORD_LENGTH = 2,
 +      MAX_WORD_LENGTH = 13,
 +      MIN_HASH_VALUE = 2,
 +      MAX_HASH_VALUE = 42
 +    };
 +
 +  static const struct env_to_conf_item wordlist[] =
 +    {
 +      {"",""}, {"",""},
 +#line 12 "envtoconfitems.gperf"
 +      {"CC", "compiler"},
- #line 15 "envtoconfitems.gperf"
++#line 17 "envtoconfitems.gperf"
 +      {"DIR", "cache_dir"},
-       {"",""},
- #line 17 "envtoconfitems.gperf"
-       {"DIRECT", "direct_mode"},
++#line 16 "envtoconfitems.gperf"
 +      {"CPP2", "run_second_cpp"},
-       {"DISABLE", "disable"},
-       {"",""},
++#line 33 "envtoconfitems.gperf"
++      {"STATS", "stats"},
 +#line 18 "envtoconfitems.gperf"
-       {"EXTENSION", "cpp_extension"},
- #line 20 "envtoconfitems.gperf"
-       {"EXTRAFILES", "extra_files_to_hash"},
++      {"DIRECT", "direct_mode"},
 +#line 19 "envtoconfitems.gperf"
-       {"PREFIX", "prefix_command"},
- #line 30 "envtoconfitems.gperf"
-       {"RECACHE", "recache"},
- #line 29 "envtoconfitems.gperf"
-       {"READONLY", "read_only"},
- #line 27 "envtoconfitems.gperf"
++      {"DISABLE", "disable"},
++#line 14 "envtoconfitems.gperf"
++      {"COMPRESS", "compression"},
 +#line 28 "envtoconfitems.gperf"
- #line 32 "envtoconfitems.gperf"
-       {"STATS", "stats"},
 +      {"PATH", "path"},
- #line 33 "envtoconfitems.gperf"
-       {"TEMPDIR", "temporary_dir"},
- #line 14 "envtoconfitems.gperf"
-       {"COMPRESS", "compression"},
++#line 36 "envtoconfitems.gperf"
++      {"UNIFY", "unify"},
 +      {"",""},
-       {"UMASK", "umask"},
++#line 31 "envtoconfitems.gperf"
++      {"RECACHE", "recache"},
++#line 15 "envtoconfitems.gperf"
++      {"COMPRESSLEVEL", "compression_level"},
++      {"",""},
++#line 32 "envtoconfitems.gperf"
++      {"SLOPPINESS", "sloppiness"},
 +      {"",""},
 +#line 34 "envtoconfitems.gperf"
- #line 31 "envtoconfitems.gperf"
-       {"SLOPPINESS", "sloppiness"},
++      {"TEMPDIR", "temporary_dir"},
++#line 30 "envtoconfitems.gperf"
++      {"READONLY", "read_only"},
++#line 20 "envtoconfitems.gperf"
++      {"EXTENSION", "cpp_extension"},
 +      {"",""},
++#line 29 "envtoconfitems.gperf"
++      {"PREFIX", "prefix_command"},
 +#line 11 "envtoconfitems.gperf"
 +      {"BASEDIR", "base_dir"},
 +#line 13 "envtoconfitems.gperf"
 +      {"COMPILERCHECK", "compiler_check"},
 +      {"",""},
- #line 25 "envtoconfitems.gperf"
++#line 21 "envtoconfitems.gperf"
++      {"EXTRAFILES", "extra_files_to_hash"},
 +      {"",""},
- #line 24 "envtoconfitems.gperf"
++#line 26 "envtoconfitems.gperf"
 +      {"MAXSIZE", "max_size"},
-       {"UNIFY", "unify"},
++#line 25 "envtoconfitems.gperf"
 +      {"MAXFILES", "max_files"},
 +      {"",""},
 +#line 35 "envtoconfitems.gperf"
-       {"LOGFILE", "log_file"},
-       {"",""}, {"",""}, {"",""}, {"",""},
- #line 22 "envtoconfitems.gperf"
++      {"UMASK", "umask"},
 +      {"",""},
 +#line 23 "envtoconfitems.gperf"
- #line 21 "envtoconfitems.gperf"
 +      {"HASHDIR", "hash_dir"},
- #line 26 "envtoconfitems.gperf"
++#line 22 "envtoconfitems.gperf"
 +      {"HARDLINK", "hard_link"},
 +      {"",""}, {"",""}, {"",""},
- static const size_t ENVTOCONFITEMS_TOTAL_KEYWORDS = 25;
++#line 24 "envtoconfitems.gperf"
++      {"LOGFILE", "log_file"},
++      {"",""}, {"",""}, {"",""}, {"",""},
++#line 27 "envtoconfitems.gperf"
 +      {"NLEVELS", "cache_dir_levels"}
 +    };
 +
 +  if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH)
 +    {
 +      register int key = envtoconfitems_hash (str, len);
 +
 +      if (key <= MAX_HASH_VALUE && key >= 0)
 +        {
 +          register const char *s = wordlist[key].env_name;
 +
 +          if (*str == *s && !strcmp (str + 1, s + 1))
 +            return &wordlist[key];
 +        }
 +    }
 +  return 0;
 +}
++static const size_t ENVTOCONFITEMS_TOTAL_KEYWORDS = 26;
index cfb00a5428a70a9e70ec5eb1253a58939c402d8e,0000000000000000000000000000000000000000..27ab441639852aeb6e0b0d88503f419da877eda3
mode 100644,000000..100644
--- /dev/null
@@@ -1,414 -1,0 +1,419 @@@
-  * Copyright (C) 2011 Joel Rosdahl
 +/*
- #define N_CONFIG_ITEMS 25
++ * Copyright (C) 2011-2012 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"
 +
-       CHECK_INT_EQ(25, n_received_conf_items);
++#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;
 +      const char *user = getenv("USER");
 +      CHECK(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"
 +              "sloppiness =     file_macro   ,time_macros,  include_file_mtime  \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_FILE_MACRO|SLOPPY_TIME_MACROS,
 +                   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 = 4294967296");
 +      CHECK(!conf_read(conf, "ccache.conf", &errmsg));
 +      CHECK_STR_EQ_FREE2("ccache.conf:1: invalid unsigned integer: \"4294967296\"",
 +                         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);
 +
 +      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_TIME_MACROS,
 +              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, time_macros",
 +                   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
diff --cc util.c
Simple merge