{"thread":{"id":"14622","subject":"[PATCH 04/12] Protect index with sparse prefix","startedAt":"2008-07-23T14:56:17Z","lastAt":"2008-07-23T14:56:17Z","messageCount":1,"participants":["Nguyễn Thái Ngọc Duy"],"isPatch":true,"patchVersion":1,"patchTotal":12},"messages":[{"id":"84509","messageId":"20080723145616.GA29090@laptop","threadId":"14622","inReplyTo":null,"subject":"[PATCH 04/12] Protect index with sparse prefix","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2008-07-23T14:56:17Z","receivedAt":"2008-07-23T14:56:17Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This ensures no one can write to index outside sparse prefix.\nProtection is usually applied to the_index only. For more\ncomplicated cases where temporary index is required, thorough\ncheck will be done by check_index_prefix()\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin-commit.c          |   11 +++++\n builtin-ls-files.c        |    2 +\n builtin-merge-recursive.c |    4 +-\n builtin-read-tree.c       |    5 ++\n cache.h                   |    1 +\n diff-lib.c                |    3 +\n read-cache.c              |  107 ++++++++++++++++++++++++++++++++++++++++++++-\n unpack-trees.c            |    3 +\n unpack-trees.h            |    3 +-\n 9 files changed, 135 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin-commit.c b/builtin-commit.c\nindex 97e64de..f2d301a 100644\n--- a/builtin-commit.c\n+++ b/builtin-commit.c\n@@ -223,6 +223,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)\n \tint fd;\n \tstruct string_list partial;\n \tconst char **pathspec = NULL;\n+\tchar **saved_sparse_prefix;\n \n \tif (interactive) {\n \t\tinteractive_add(argc, argv, prefix);\n@@ -306,6 +307,13 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)\n \n \tmemset(&partial, 0, sizeof(partial));\n \tpartial.strdup_strings = 1;\n+\n+\t/*\n+\t * this code modifies index but won't write down anything\n+\t * so it's safe to turn of sparse_prefix protection for a while\n+\t */\n+\tsaved_sparse_prefix = save_sparse_prefix();\n+\n \tif (list_paths(&partial, initial_commit ? NULL : \"HEAD\", prefix, pathspec))\n \t\texit(1);\n \n@@ -313,6 +321,9 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)\n \tif (read_cache() < 0)\n \t\tdie(\"cannot read the index\");\n \n+\t/* re-enable sparse_prefix again */\n+\trestore_sparse_prefix(saved_sparse_prefix);\n+\n \tfd = hold_locked_index(&index_lock, 1);\n \tadd_remove_files(&partial);\n \trefresh_cache(REFRESH_QUIET);\ndiff --git a/builtin-ls-files.c b/builtin-ls-files.c\nindex e8d568e..4d18873 100644\n--- a/builtin-ls-files.c\n+++ b/builtin-ls-files.c\n@@ -606,6 +606,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)\n \t\t */\n \t\tif (show_stage || show_unmerged)\n \t\t\tdie(\"ls-files --with-tree is incompatible with -s or -u\");\n+\t\t/* no need to restore as ls-files never writes index */\n+\t\tsave_sparse_prefix();\n \t\toverlay_tree_on_cache(with_tree, prefix);\n \t}\n \tshow_files(&dir, prefix);\ndiff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c\nindex 43e55bf..7ce015b 100644\n--- a/builtin-merge-recursive.c\n+++ b/builtin-merge-recursive.c\n@@ -201,8 +201,10 @@ static int git_merge_trees(int index_only,\n \tmemset(&opts, 0, sizeof(opts));\n \tif (index_only)\n \t\topts.index_only = 1;\n-\telse\n+\telse {\n \t\topts.update = 1;\n+\t\topts.check_index_prefix = 1;\n+\t}\n \topts.merge = 1;\n \topts.head_idx = 2;\n \topts.fn = threeway_merge;\ndiff --git a/builtin-read-tree.c b/builtin-read-tree.c\nindex 72a6de3..0883b41 100644\n--- a/builtin-read-tree.c\n+++ b/builtin-read-tree.c\n@@ -218,6 +218,11 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)\n \t\t\topts.head_idx = 1;\n \t}\n \n+\tif (get_sparse_prefix()) {\n+\t\tread_cache();\n+\t\topts.check_index_prefix = 1;\n+\t}\n+\n \tfor (i = 0; i < nr_trees; i++) {\n \t\tstruct tree *tree = trees[i];\n \t\tparse_tree(tree);\ndiff --git a/cache.h b/cache.h\nindex 4687096..f6bbadc 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -331,6 +331,7 @@ extern char **split_prefix(const char *env);\n extern char **combine_prefix_list(char **p1, char **p2);\n extern void free_prefix_list(char **prefix_list);\n extern int outside_prefix_list(char **iprefix, const char *prefix);\n+extern int check_index_prefix(const struct index_state *index, int is_merge);\n \n #define ALTERNATE_DB_ENVIRONMENT \"GIT_ALTERNATE_OBJECT_DIRECTORIES\"\n \ndiff --git a/diff-lib.c b/diff-lib.c\nindex e7eaff9..2908146 100644\n--- a/diff-lib.c\n+++ b/diff-lib.c\n@@ -73,6 +73,9 @@ int run_diff_files(struct rev_info *revs, unsigned int option)\n \t\tstruct cache_entry *ce = active_cache[i];\n \t\tint changed;\n \n+\t\tif (index_outside_sparse_prefix(ce->name))\n+\t\t\tcontinue;\n+\n \t\tif (DIFF_OPT_TST(&revs->diffopt, QUIET) &&\n \t\t\tDIFF_OPT_TST(&revs->diffopt, HAS_CHANGES))\n \t\t\tbreak;\ndiff --git a/read-cache.c b/read-cache.c\nindex a50a851..4fea40e 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -23,6 +23,11 @@\n \n struct index_state the_index;\n \n+static int outside_cache_prefix(const struct index_state *istate, const char *ce_name)\n+{\n+\treturn istate == &the_index && index_outside_sparse_prefix(ce_name);\n+}\n+\n static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)\n {\n \tistate->cache[nr] = ce;\n@@ -396,6 +401,8 @@ int remove_index_entry_at(struct index_state *istate, int pos)\n {\n \tstruct cache_entry *ce = istate->cache[pos];\n \n+\tif (outside_cache_prefix(istate, ce->name))\n+\t\tdie(\"%s: cannot remove from index outside %s\", ce->name, get_sparse_prefix_str());\n \tremove_name_hash(ce);\n \tistate->cache_changed = 1;\n \tistate->cache_nr--;\n@@ -410,6 +417,10 @@ int remove_index_entry_at(struct index_state *istate, int pos)\n int remove_file_from_index(struct index_state *istate, const char *path)\n {\n \tint pos = index_name_pos(istate, path, strlen(path));\n+\n+\tif (outside_cache_prefix(istate, path))\n+\t\tdie(\"%s: cannot remove from index from outside %s\", path, get_sparse_prefix_str());\n+\n \tif (pos < 0)\n \t\tpos = -pos-1;\n \tcache_tree_invalidate_path(istate->cache_tree, path);\n@@ -809,21 +820,43 @@ static int check_file_directory_conflict(struct index_state *istate,\n \treturn retval + has_dir_name(istate, ce, pos, ok_to_replace);\n }\n \n+/*\n+ * ce_compare only considers stuff that will be written to a tree object\n+ * or index stages\n+ *\n+ * On CE_* flags CE_HASHED, CE_UNHASHED and CE_UPTODATE are out because they\n+ * are in-core only, will not be written out\n+ */\n+#define CE_COMPARE_MASK (CE_STATE_MASK | CE_UPTODATE)\n+static int ce_compare(const struct cache_entry *ce1, const struct cache_entry *ce2)\n+{\n+\treturn ce1->ce_mode == ce2->ce_mode &&\n+\t\t((ce1->ce_flags ^ ce2->ce_flags) & ~CE_COMPARE_MASK) == 0 &&\n+\t\t!memcmp(ce1->sha1, ce2->sha1, 20) &&\n+\t\t!strcmp(ce1->name, ce2->name);\n+}\n+\n static int add_index_entry_with_check(struct index_state *istate, struct cache_entry *ce, int option)\n {\n \tint pos;\n \tint ok_to_add = option & ADD_CACHE_OK_TO_ADD;\n \tint ok_to_replace = option & ADD_CACHE_OK_TO_REPLACE;\n \tint skip_df_check = option & ADD_CACHE_SKIP_DFCHECK;\n+\tint is_outside = outside_cache_prefix(istate, ce->name);\n \n \tcache_tree_invalidate_path(istate->cache_tree, ce->name);\n \tpos = index_name_pos(istate, ce->name, ce->ce_flags);\n \n \t/* existing match? Just replace it. */\n \tif (pos >= 0) {\n+\t\tif (is_outside && !ce_compare(istate->cache[pos], ce))\n+\t\t\tdie(\"%s: cannot add to index outside %s\", ce->name, get_sparse_prefix_str());\n \t\treplace_index_entry(istate, pos, ce);\n \t\treturn 0;\n \t}\n+\n+\tif (is_outside)\n+\t\tdie(\"%s: cannot add to index outside %s\", ce->name, get_sparse_prefix_str());\n \tpos = -pos-1;\n \n \t/*\n@@ -858,9 +891,11 @@ int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti\n {\n \tint pos;\n \n-\tif (option & ADD_CACHE_JUST_APPEND)\n+\tif (option & ADD_CACHE_JUST_APPEND) {\n+\t\tif (outside_cache_prefix(istate, ce->name))\n+\t\t\tdie(\"%s: cannot add to index outside %s\", ce->name, get_sparse_prefix_str());\n \t\tpos = istate->cache_nr;\n-\telse {\n+\t} else {\n \t\tint ret;\n \t\tret = add_index_entry_with_check(istate, ce, option);\n \t\tif (ret <= 0)\n@@ -1444,3 +1479,71 @@ int read_index_unmerged(struct index_state *istate)\n \tistate->cache_nr = dst - istate->cache;\n \treturn !!last;\n }\n+\n+int check_index_prefix(const struct index_state *index, int is_merge)\n+{\n+\tunsigned i;\n+\tchar **index_prefix = get_sparse_prefix();\n+\n+\tif (!index_prefix)\n+\t\treturn 1;\n+\n+\t/* Check for unmerged entries first */\n+\tfor (i = 0; i < index->cache_nr; i++) {\n+\t\tstruct cache_entry *ce = index->cache[i];\n+\t\tif (ce_stage(ce) && index_outside_sparse_prefix(ce->name))\n+\t\t\treturn 0;\n+\t}\n+\n+\t/*\n+\t * if is_merge is true, caller has decided that there\n+\t * could be changes outside index prefix, return now\n+\t */\n+\tif (is_merge)\n+\t\treturn 1;\n+\telse {\n+\t\tconst struct index_state *idx[2];\n+\t\tint prefix_i[2];\n+\t\tint index_prefix_nr;\n+\t\tunsigned entry_i[2];\n+\n+\t\tidx[0] = &the_index;\n+\t\tidx[1] = index;\n+\t\tprefix_i[0] = prefix_i[1] = 0;\n+\t\tentry_i[0] = entry_i[1] = 0;\n+\t\tindex_prefix_nr = 0;\n+\t\twhile (index_prefix[index_prefix_nr])\n+\t\t\tindex_prefix_nr++;\n+\n+\t\twhile (entry_i[0] < idx[0]->cache_nr &&\n+\t\t       entry_i[1] < idx[1]->cache_nr) {\n+\t\t\t/* ignore anything inside index prefix */\n+\t\t\tint what_index;\n+\t\t\tfor (what_index = 0; what_index < 2; what_index++) {\n+\t\t\t\tconst struct index_state *idx_p = idx[what_index];\n+\t\t\t\tconst char *prefix = index_prefix[prefix_i[what_index]];\n+\t\t\t\tunsigned *entry_ip = entry_i+what_index;\n+\t\t\t\tif (prefix_i[what_index] < index_prefix_nr &&\n+\t\t\t\t    !prefixcmp(idx_p->cache[*entry_ip]->name, prefix)) {\n+\t\t\t\t\twhile (*entry_ip < idx_p->cache_nr &&\n+\t\t\t\t\t       !prefixcmp(idx_p->cache[*entry_ip]->name, prefix))\n+\t\t\t\t\t\tentry_ip[0]++;\n+\t\t\t\t\tprefix_i[what_index]++;\n+\t\t\t\t}\n+\t\t\t}\n+\n+\t\t\tif (entry_i[0] == idx[0]->cache_nr || entry_i[1] == idx[1]->cache_nr)\n+\t\t\t\tbreak;\n+\n+\t\t\t/* does not match */\n+\t\t\tif (!ce_compare(idx[0]->cache[entry_i[0]], idx[1]->cache[entry_i[1]]))\n+\t\t\t\treturn 0;\n+\n+\t\t\tentry_i[0]++;\n+\t\t\tentry_i[1]++;\n+\t\t}\n+\n+\t\treturn entry_i[0] == idx[0]->cache_nr &&\n+\t\t\tentry_i[1] == idx[1]->cache_nr;\n+\t}\n+}\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex cba0aca..0a6723b 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -408,6 +408,9 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options\n \tif (o->trivial_merges_only && o->nontrivial_merge)\n \t\treturn unpack_failed(o, \"Merge requires file-level merging\");\n \n+\tif (o->check_index_prefix && !check_index_prefix(&o->result, o->merge && !o->prefix))\n+\t\treturn unpack_failed(o, \"Merge outside index prefix\");\n+\n \to->src_index = NULL;\n \tret = check_updates(o) ? (-2) : 0;\n \tif (o->dst_index)\ndiff --git a/unpack-trees.h b/unpack-trees.h\nindex 94e5672..a1b46f9 100644\n--- a/unpack-trees.h\n+++ b/unpack-trees.h\n@@ -26,7 +26,8 @@ struct unpack_trees_options {\n \t\t     verbose_update:1,\n \t\t     aggressive:1,\n \t\t     skip_unmerged:1,\n-\t\t     gently:1;\n+\t\t     gently:1,\n+\t\t     check_index_prefix:1;\n \tconst char *prefix;\n \tint pos;\n \tstruct dir_struct *dir;\n-- \n1.5.5.GIT\n"}]}