Merge branch 'nd/config-misc-fixes'
authorJunio C Hamano <gitster@pobox.com>
Tue, 10 Jan 2017 23:24:27 +0000 (15:24 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 10 Jan 2017 23:24:27 +0000 (15:24 -0800)
Leakage of lockfiles in the config subsystem has been fixed.

* nd/config-misc-fixes:
config.c: handle lock file in error case in git_config_rename_...
config.c: rename label unlock_and_out
config.c: handle error case for fstat() calls

1  2 
config.c
diff --combined config.c
index 0b9c1b62bbb5fb04a907f8794b8d375ffcf396ca,398ffec29d6a471edfa208a58a742de99df9d6d3..617b2e3cf4a9dcb61283ea797166d2bda812de6f
+++ b/config.c
@@@ -24,7 -24,7 +24,7 @@@ struct config_source 
                        size_t pos;
                } buf;
        } u;
 -      const char *origin_type;
 +      enum config_origin_type origin_type;
        const char *name;
        const char *path;
        int die_on_error;
        long (*do_ftell)(struct config_source *c);
  };
  
 +/*
 + * These variables record the "current" config source, which
 + * can be accessed by parsing callbacks.
 + *
 + * The "cf" variable will be non-NULL only when we are actually parsing a real
 + * config source (file, blob, cmdline, etc).
 + *
 + * The "current_config_kvi" variable will be non-NULL only when we are feeding
 + * cached config from a configset into a callback.
 + *
 + * They should generally never be non-NULL at the same time. If they are both
 + * NULL, then we aren't parsing anything (and depending on the function looking
 + * at the variables, it's either a bug for it to be called in the first place,
 + * or it's a function which can be reused for non-config purposes, and should
 + * fall back to some sane behavior).
 + */
  static struct config_source *cf;
 +static struct key_value_info *current_config_kvi;
  
 +/*
 + * Similar to the variables above, this gives access to the "scope" of the
 + * current value (repo, global, etc). For cached values, it can be found via
 + * the current_config_kvi as above. During parsing, the current value can be
 + * found in this variable. It's not part of "cf" because it transcends a single
 + * file (i.e., a file included from .git/config is still in "repo" scope).
 + */
 +static enum config_scope current_parsing_scope;
 +
 +static int core_compression_seen;
 +static int pack_compression_seen;
  static int zlib_compression_seen;
  
  /*
@@@ -159,9 -131,7 +159,9 @@@ static int handle_path_include(const ch
        if (!access_or_die(path, R_OK, 0)) {
                if (++inc->depth > MAX_INCLUDE_DEPTH)
                        die(include_depth_advice, MAX_INCLUDE_DEPTH, path,
 -                          cf && cf->name ? cf->name : "the command line");
 +                          !cf ? "<unknown>" :
 +                          cf->name ? cf->name :
 +                          "the command line");
                ret = git_config_from_file(git_config_include, path, inc);
                inc->depth--;
        }
@@@ -235,41 -205,32 +235,41 @@@ int git_config_parse_parameter(const ch
  int git_config_from_parameters(config_fn_t fn, void *data)
  {
        const char *env = getenv(CONFIG_DATA_ENVIRONMENT);
 +      int ret = 0;
        char *envw;
        const char **argv = NULL;
        int nr = 0, alloc = 0;
        int i;
 +      struct config_source source;
  
        if (!env)
                return 0;
 +
 +      memset(&source, 0, sizeof(source));
 +      source.prev = cf;
 +      source.origin_type = CONFIG_ORIGIN_CMDLINE;
 +      cf = &source;
 +
        /* sq_dequote will write over it */
        envw = xstrdup(env);
  
        if (sq_dequote_to_argv(envw, &argv, &nr, &alloc) < 0) {
 -              free(envw);
 -              return error("bogus format in " CONFIG_DATA_ENVIRONMENT);
 +              ret = error("bogus format in " CONFIG_DATA_ENVIRONMENT);
 +              goto out;
        }
  
        for (i = 0; i < nr; i++) {
                if (git_config_parse_parameter(argv[i], fn, data) < 0) {
 -                      free(argv);
 -                      free(envw);
 -                      return -1;
 +                      ret = -1;
 +                      goto out;
                }
        }
  
 +out:
        free(argv);
        free(envw);
 -      return nr > 0;
 +      cf = source.prev;
 +      return ret;
  }
  
  static int get_next_char(void)
@@@ -456,8 -417,6 +456,8 @@@ static int git_parse_source(config_fn_
        int comment = 0;
        int baselen = 0;
        struct strbuf *var = &cf->var;
 +      int error_return = 0;
 +      char *error_msg = NULL;
  
        /* U+FEFF Byte Order Mark in UTF8 */
        const char *bomptr = utf8_bom;
                if (get_value(fn, data, var) < 0)
                        break;
        }
 +
 +      switch (cf->origin_type) {
 +      case CONFIG_ORIGIN_BLOB:
 +              error_msg = xstrfmt(_("bad config line %d in blob %s"),
 +                                    cf->linenr, cf->name);
 +              break;
 +      case CONFIG_ORIGIN_FILE:
 +              error_msg = xstrfmt(_("bad config line %d in file %s"),
 +                                    cf->linenr, cf->name);
 +              break;
 +      case CONFIG_ORIGIN_STDIN:
 +              error_msg = xstrfmt(_("bad config line %d in standard input"),
 +                                    cf->linenr);
 +              break;
 +      case CONFIG_ORIGIN_SUBMODULE_BLOB:
 +              error_msg = xstrfmt(_("bad config line %d in submodule-blob %s"),
 +                                     cf->linenr, cf->name);
 +              break;
 +      case CONFIG_ORIGIN_CMDLINE:
 +              error_msg = xstrfmt(_("bad config line %d in command line %s"),
 +                                     cf->linenr, cf->name);
 +              break;
 +      default:
 +              error_msg = xstrfmt(_("bad config line %d in %s"),
 +                                    cf->linenr, cf->name);
 +      }
 +
        if (cf->die_on_error)
 -              die(_("bad config line %d in %s %s"), cf->linenr, cf->origin_type, cf->name);
 +              die("%s", error_msg);
        else
 -              return error(_("bad config line %d in %s %s"), cf->linenr, cf->origin_type, cf->name);
 +              error_return = error("%s", error_msg);
 +
 +      free(error_msg);
 +      return error_return;
  }
  
  static int parse_unit_factor(const char *end, uintmax_t *val)
@@@ -654,35 -583,16 +654,35 @@@ int git_parse_ulong(const char *value, 
  NORETURN
  static void die_bad_number(const char *name, const char *value)
  {
 -      const char *reason = errno == ERANGE ?
 -                           "out of range" :
 -                           "invalid unit";
 +      const char * error_type = (errno == ERANGE)? _("out of range"):_("invalid unit");
 +
        if (!value)
                value = "";
  
 -      if (cf && cf->origin_type && cf->name)
 -              die(_("bad numeric config value '%s' for '%s' in %s %s: %s"),
 -                  value, name, cf->origin_type, cf->name, reason);
 -      die(_("bad numeric config value '%s' for '%s': %s"), value, name, reason);
 +      if (!(cf && cf->name))
 +              die(_("bad numeric config value '%s' for '%s': %s"),
 +                  value, name, error_type);
 +
 +      switch (cf->origin_type) {
 +      case CONFIG_ORIGIN_BLOB:
 +              die(_("bad numeric config value '%s' for '%s' in blob %s: %s"),
 +                  value, name, cf->name, error_type);
 +      case CONFIG_ORIGIN_FILE:
 +              die(_("bad numeric config value '%s' for '%s' in file %s: %s"),
 +                  value, name, cf->name, error_type);
 +      case CONFIG_ORIGIN_STDIN:
 +              die(_("bad numeric config value '%s' for '%s' in standard input: %s"),
 +                  value, name, error_type);
 +      case CONFIG_ORIGIN_SUBMODULE_BLOB:
 +              die(_("bad numeric config value '%s' for '%s' in submodule-blob %s: %s"),
 +                  value, name, cf->name, error_type);
 +      case CONFIG_ORIGIN_CMDLINE:
 +              die(_("bad numeric config value '%s' for '%s' in command line %s: %s"),
 +                  value, name, cf->name, error_type);
 +      default:
 +              die(_("bad numeric config value '%s' for '%s' in %s: %s"),
 +                  value, name, cf->name, error_type);
 +      }
  }
  
  int git_config_int(const char *name, const char *value)
@@@ -836,22 -746,13 +836,22 @@@ static int git_default_core_config(cons
        }
  
        if (!strcmp(var, "core.abbrev")) {
 -              int abbrev = git_config_int(var, value);
 -              if (abbrev < minimum_abbrev || abbrev > 40)
 -                      return -1;
 -              default_abbrev = abbrev;
 +              if (!value)
 +                      return config_error_nonbool(var);
 +              if (!strcasecmp(value, "auto"))
 +                      default_abbrev = -1;
 +              else {
 +                      int abbrev = git_config_int(var, value);
 +                      if (abbrev < minimum_abbrev || abbrev > 40)
 +                              return error("abbrev length out of range: %d", abbrev);
 +                      default_abbrev = abbrev;
 +              }
                return 0;
        }
  
 +      if (!strcmp(var, "core.disambiguate"))
 +              return set_disambiguate_hint_config(var, value);
 +
        if (!strcmp(var, "core.loosecompression")) {
                int level = git_config_int(var, value);
                if (level == -1)
                core_compression_seen = 1;
                if (!zlib_compression_seen)
                        zlib_compression_level = level;
 +              if (!pack_compression_seen)
 +                      pack_compression_level = level;
                return 0;
        }
  
                return 0;
        }
  
 -      if (!strcmp(var, "core.pager"))
 -              return git_config_string(&pager_program, var, value);
 -
        if (!strcmp(var, "core.editor"))
                return git_config_string(&editor_program, var, value);
  
@@@ -1135,18 -1037,6 +1135,18 @@@ int git_default_config(const char *var
                pack_size_limit_cfg = git_config_ulong(var, value);
                return 0;
        }
 +
 +      if (!strcmp(var, "pack.compression")) {
 +              int level = git_config_int(var, value);
 +              if (level == -1)
 +                      level = Z_DEFAULT_COMPRESSION;
 +              else if (level < 0 || level > Z_BEST_COMPRESSION)
 +                      die(_("bad pack compression level %d"), level);
 +              pack_compression_level = level;
 +              pack_compression_seen = 1;
 +              return 0;
 +      }
 +
        /* Add other config variables here and to Documentation/config.txt. */
        return 0;
  }
@@@ -1179,8 -1069,7 +1179,8 @@@ static int do_config_from(struct config
  }
  
  static int do_config_from_file(config_fn_t fn,
 -              const char *origin_type, const char *name, const char *path, FILE *f,
 +              const enum config_origin_type origin_type,
 +              const char *name, const char *path, FILE *f,
                void *data)
  {
        struct config_source top;
  
  static int git_config_from_stdin(config_fn_t fn, void *data)
  {
 -      return do_config_from_file(fn, "standard input", "", NULL, stdin, data);
 +      return do_config_from_file(fn, CONFIG_ORIGIN_STDIN, "", NULL, stdin, data);
  }
  
  int git_config_from_file(config_fn_t fn, const char *filename, void *data)
        f = fopen(filename, "r");
        if (f) {
                flockfile(f);
 -              ret = do_config_from_file(fn, "file", filename, filename, f, data);
 +              ret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename, filename, f, data);
                funlockfile(f);
                fclose(f);
        }
        return ret;
  }
  
 -int git_config_from_mem(config_fn_t fn, const char *origin_type,
 +int git_config_from_mem(config_fn_t fn, const enum config_origin_type origin_type,
                        const char *name, const char *buf, size_t len, void *data)
  {
        struct config_source top;
@@@ -1254,7 -1143,7 +1254,7 @@@ static int git_config_from_blob_sha1(co
                return error("reference '%s' does not point to a blob", name);
        }
  
 -      ret = git_config_from_mem(fn, "blob", name, buf, size, data);
 +      ret = git_config_from_mem(fn, CONFIG_ORIGIN_BLOB, name, buf, size, data);
        free(buf);
  
        return ret;
@@@ -1308,36 -1197,47 +1308,36 @@@ int git_config_system(void
  
  static int do_git_config_sequence(config_fn_t fn, void *data)
  {
 -      int ret = 0, found = 0;
 +      int ret = 0;
        char *xdg_config = xdg_config_home("config");
        char *user_config = expand_user_path("~/.gitconfig");
 -      char *repo_config = git_pathdup("config");
 +      char *repo_config = have_git_dir() ? git_pathdup("config") : NULL;
  
 -      if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0)) {
 +      current_parsing_scope = CONFIG_SCOPE_SYSTEM;
 +      if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0))
                ret += git_config_from_file(fn, git_etc_gitconfig(),
                                            data);
 -              found += 1;
 -      }
  
 -      if (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK)) {
 +      current_parsing_scope = CONFIG_SCOPE_GLOBAL;
 +      if (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))
                ret += git_config_from_file(fn, xdg_config, data);
 -              found += 1;
 -      }
  
 -      if (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK)) {
 +      if (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))
                ret += git_config_from_file(fn, user_config, data);
 -              found += 1;
 -      }
  
 -      if (repo_config && !access_or_die(repo_config, R_OK, 0)) {
 +      current_parsing_scope = CONFIG_SCOPE_REPO;
 +      if (repo_config && !access_or_die(repo_config, R_OK, 0))
                ret += git_config_from_file(fn, repo_config, data);
 -              found += 1;
 -      }
  
 -      switch (git_config_from_parameters(fn, data)) {
 -      case -1: /* error */
 +      current_parsing_scope = CONFIG_SCOPE_CMDLINE;
 +      if (git_config_from_parameters(fn, data) < 0)
                die(_("unable to parse command-line config"));
 -              break;
 -      case 0: /* found nothing */
 -              break;
 -      default: /* found at least one item */
 -              found++;
 -              break;
 -      }
  
 +      current_parsing_scope = CONFIG_SCOPE_UNKNOWN;
        free(xdg_config);
        free(user_config);
        free(repo_config);
 -      return ret == 0 ? found : ret;
 +      return ret;
  }
  
  int git_config_with_options(config_fn_t fn, void *data,
@@@ -1372,7 -1272,7 +1372,7 @@@ static void git_config_raw(config_fn_t 
        if (git_config_with_options(fn, data, NULL, 1) < 0)
                /*
                 * git_config_with_options() normally returns only
 -               * positive values, as most errors are fatal, and
 +               * zero, as most errors are fatal, and
                 * non-fatal potential errors are guarded by "if"
                 * statements that are entered only when no error is
                 * possible.
@@@ -1390,20 -1290,16 +1390,20 @@@ static void configset_iter(struct confi
        struct string_list *values;
        struct config_set_element *entry;
        struct configset_list *list = &cs->list;
 -      struct key_value_info *kv_info;
  
        for (i = 0; i < list->nr; i++) {
                entry = list->items[i].e;
                value_index = list->items[i].value_index;
                values = &entry->value_list;
 -              if (fn(entry->key, values->items[value_index].string, data) < 0) {
 -                      kv_info = values->items[value_index].util;
 -                      git_die_config_linenr(entry->key, kv_info->filename, kv_info->linenr);
 -              }
 +
 +              current_config_kvi = values->items[value_index].util;
 +
 +              if (fn(entry->key, values->items[value_index].string, data) < 0)
 +                      git_die_config_linenr(entry->key,
 +                                            current_config_kvi->filename,
 +                                            current_config_kvi->linenr);
 +
 +              current_config_kvi = NULL;
        }
  }
  
@@@ -1460,19 -1356,14 +1460,19 @@@ static int configset_add_value(struct c
        l_item->e = e;
        l_item->value_index = e->value_list.nr - 1;
  
 -      if (cf) {
 +      if (!cf)
 +              die("BUG: configset_add_value has no source");
 +      if (cf->name) {
                kv_info->filename = strintern(cf->name);
                kv_info->linenr = cf->linenr;
 +              kv_info->origin_type = cf->origin_type;
        } else {
                /* for values read from `git_config_from_parameters()` */
                kv_info->filename = NULL;
                kv_info->linenr = -1;
 +              kv_info->origin_type = CONFIG_ORIGIN_CMDLINE;
        }
 +      kv_info->scope = current_parsing_scope;
        si->util = kv_info;
  
        return 0;
@@@ -2216,7 -2107,12 +2216,12 @@@ int git_config_set_multivar_in_file_gen
                        goto out_free;
                }
  
-               fstat(in_fd, &st);
+               if (fstat(in_fd, &st) == -1) {
+                       error_errno(_("fstat on %s failed"), config_filename);
+                       ret = CONFIG_INVALID_FILE;
+                       goto out_free;
+               }
                contents_sz = xsize_t(st.st_size);
                contents = xmmap_gently(NULL, contents_sz, PROT_READ,
                                        MAP_PRIVATE, in_fd, 0);
@@@ -2418,7 -2314,7 +2423,7 @@@ int git_config_rename_section_in_file(c
  
        if (new_name && !section_name_is_ok(new_name)) {
                ret = error("invalid section name: %s", new_name);
-               goto out;
+               goto out_no_rollback;
        }
  
        if (!config_filename)
  
        if (!(config_file = fopen(config_filename, "rb"))) {
                /* no config file means nothing to rename, no error */
-               goto unlock_and_out;
+               goto commit_and_out;
        }
  
-       fstat(fileno(config_file), &st);
+       if (fstat(fileno(config_file), &st) == -1) {
+               ret = error_errno(_("fstat on %s failed"), config_filename);
+               goto out;
+       }
  
        if (chmod(get_lock_file_path(lock), st.st_mode & 07777) < 0) {
                ret = error_errno("chmod on %s failed",
                }
        }
        fclose(config_file);
unlock_and_out:
commit_and_out:
        if (commit_lock_file(lock) < 0)
                ret = error_errno("could not write config file %s",
                                  config_filename);
  out:
+       rollback_lock_file(lock);
+ out_no_rollback:
        free(filename_buf);
        return ret;
  }
@@@ -2551,46 -2452,10 +2561,46 @@@ int parse_config_key(const char *var
  
  const char *current_config_origin_type(void)
  {
 -      return cf && cf->origin_type ? cf->origin_type : "command line";
 +      int type;
 +      if (current_config_kvi)
 +              type = current_config_kvi->origin_type;
 +      else if(cf)
 +              type = cf->origin_type;
 +      else
 +              die("BUG: current_config_origin_type called outside config callback");
 +
 +      switch (type) {
 +      case CONFIG_ORIGIN_BLOB:
 +              return "blob";
 +      case CONFIG_ORIGIN_FILE:
 +              return "file";
 +      case CONFIG_ORIGIN_STDIN:
 +              return "standard input";
 +      case CONFIG_ORIGIN_SUBMODULE_BLOB:
 +              return "submodule-blob";
 +      case CONFIG_ORIGIN_CMDLINE:
 +              return "command line";
 +      default:
 +              die("BUG: unknown config origin type");
 +      }
  }
  
  const char *current_config_name(void)
  {
 -      return cf && cf->name ? cf->name : "";
 +      const char *name;
 +      if (current_config_kvi)
 +              name = current_config_kvi->filename;
 +      else if (cf)
 +              name = cf->name;
 +      else
 +              die("BUG: current_config_name called outside config callback");
 +      return name ? name : "";
 +}
 +
 +enum config_scope current_config_scope(void)
 +{
 +      if (current_config_kvi)
 +              return current_config_kvi->scope;
 +      else
 +              return current_parsing_scope;
  }