refs: prevent accidental NULL dereference in write_pseudoref
[gitweb.git] / refs.c
diff --git a/refs.c b/refs.c
index 83f0e687857dfaee3b91ce4d47b16f2f16945ce2..91c2af78b627ef242bb61f8853c6dd30b2551b66 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -239,8 +239,7 @@ int read_ref(const char *refname, unsigned char *sha1)
 
 int ref_exists(const char *refname)
 {
-       unsigned char sha1[20];
-       return !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, sha1, NULL);
+       return !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, NULL, NULL);
 }
 
 static int filter_refs(const char *refname, const struct object_id *oid,
@@ -286,12 +285,11 @@ static int warn_if_dangling_symref(const char *refname, const struct object_id *
 {
        struct warn_if_dangling_data *d = cb_data;
        const char *resolves_to;
-       struct object_id junk;
 
        if (!(flags & REF_ISSYMREF))
                return 0;
 
-       resolves_to = resolve_ref_unsafe(refname, 0, junk.hash, NULL);
+       resolves_to = resolve_ref_unsafe(refname, 0, NULL, NULL);
        if (!resolves_to
            || (d->refname
                ? strcmp(resolves_to, d->refname)
@@ -576,8 +574,8 @@ long get_files_ref_lock_timeout_ms(void)
        return timeout_ms;
 }
 
-static int write_pseudoref(const char *pseudoref, const unsigned char *sha1,
-                          const unsigned char *old_sha1, struct strbuf *err)
+static int write_pseudoref(const char *pseudoref, const struct object_id *oid,
+                          const struct object_id *old_oid, struct strbuf *err)
 {
        const char *filename;
        int fd;
@@ -585,7 +583,10 @@ static int write_pseudoref(const char *pseudoref, const unsigned char *sha1,
        struct strbuf buf = STRBUF_INIT;
        int ret = -1;
 
-       strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1));
+       if (!oid)
+               return 0;
+
+       strbuf_addf(&buf, "%s\n", oid_to_hex(oid));
 
        filename = git_path("%s", pseudoref);
        fd = hold_lock_file_for_update_timeout(&lock, filename,
@@ -597,12 +598,12 @@ static int write_pseudoref(const char *pseudoref, const unsigned char *sha1,
                goto done;
        }
 
-       if (old_sha1) {
-               unsigned char actual_old_sha1[20];
+       if (old_oid) {
+               struct object_id actual_old_oid;
 
-               if (read_ref(pseudoref, actual_old_sha1))
+               if (read_ref(pseudoref, actual_old_oid.hash))
                        die("could not read ref '%s'", pseudoref);
-               if (hashcmp(actual_old_sha1, old_sha1)) {
+               if (oidcmp(&actual_old_oid, old_oid)) {
                        strbuf_addf(err, "unexpected sha1 when writing '%s'", pseudoref);
                        rollback_lock_file(&lock);
                        goto done;
@@ -622,25 +623,25 @@ static int write_pseudoref(const char *pseudoref, const unsigned char *sha1,
        return ret;
 }
 
-static int delete_pseudoref(const char *pseudoref, const unsigned char *old_sha1)
+static int delete_pseudoref(const char *pseudoref, const struct object_id *old_oid)
 {
        static struct lock_file lock;
        const char *filename;
 
        filename = git_path("%s", pseudoref);
 
-       if (old_sha1 && !is_null_sha1(old_sha1)) {
+       if (old_oid && !is_null_oid(old_oid)) {
                int fd;
-               unsigned char actual_old_sha1[20];
+               struct object_id actual_old_oid;
 
                fd = hold_lock_file_for_update_timeout(
                                &lock, filename, LOCK_DIE_ON_ERROR,
                                get_files_ref_lock_timeout_ms());
                if (fd < 0)
                        die_errno(_("Could not open '%s' for writing"), filename);
-               if (read_ref(pseudoref, actual_old_sha1))
+               if (read_ref(pseudoref, actual_old_oid.hash))
                        die("could not read ref '%s'", pseudoref);
-               if (hashcmp(actual_old_sha1, old_sha1)) {
+               if (oidcmp(&actual_old_oid, old_oid)) {
                        warning("Unexpected sha1 when deleting %s", pseudoref);
                        rollback_lock_file(&lock);
                        return -1;
@@ -657,7 +658,7 @@ static int delete_pseudoref(const char *pseudoref, const unsigned char *old_sha1
 
 int refs_delete_ref(struct ref_store *refs, const char *msg,
                    const char *refname,
-                   const unsigned char *old_sha1,
+                   const struct object_id *old_oid,
                    unsigned int flags)
 {
        struct ref_transaction *transaction;
@@ -665,12 +666,13 @@ int refs_delete_ref(struct ref_store *refs, const char *msg,
 
        if (ref_type(refname) == REF_TYPE_PSEUDOREF) {
                assert(refs == get_main_ref_store());
-               return delete_pseudoref(refname, old_sha1);
+               return delete_pseudoref(refname, old_oid);
        }
 
        transaction = ref_store_transaction_begin(refs, &err);
        if (!transaction ||
-           ref_transaction_delete(transaction, refname, old_sha1,
+           ref_transaction_delete(transaction, refname,
+                                  old_oid ? old_oid->hash : NULL,
                                   flags, msg, &err) ||
            ref_transaction_commit(transaction, &err)) {
                error("%s", err.buf);
@@ -684,10 +686,10 @@ int refs_delete_ref(struct ref_store *refs, const char *msg,
 }
 
 int delete_ref(const char *msg, const char *refname,
-              const unsigned char *old_sha1, unsigned int flags)
+              const struct object_id *old_oid, unsigned int flags)
 {
        return refs_delete_ref(get_main_ref_store(), msg, refname,
-                              old_sha1, flags);
+                              old_oid, flags);
 }
 
 int copy_reflog_msg(char *buf, const char *msg)
@@ -986,17 +988,9 @@ int ref_transaction_verify(struct ref_transaction *transaction,
                                      flags, NULL, err);
 }
 
-int update_ref_oid(const char *msg, const char *refname,
-              const struct object_id *new_oid, const struct object_id *old_oid,
-              unsigned int flags, enum action_on_err onerr)
-{
-       return update_ref(msg, refname, new_oid ? new_oid->hash : NULL,
-               old_oid ? old_oid->hash : NULL, flags, onerr);
-}
-
 int refs_update_ref(struct ref_store *refs, const char *msg,
-                   const char *refname, const unsigned char *new_sha1,
-                   const unsigned char *old_sha1, unsigned int flags,
+                   const char *refname, const struct object_id *new_oid,
+                   const struct object_id *old_oid, unsigned int flags,
                    enum action_on_err onerr)
 {
        struct ref_transaction *t = NULL;
@@ -1005,11 +999,12 @@ int refs_update_ref(struct ref_store *refs, const char *msg,
 
        if (ref_type(refname) == REF_TYPE_PSEUDOREF) {
                assert(refs == get_main_ref_store());
-               ret = write_pseudoref(refname, new_sha1, old_sha1, &err);
+               ret = write_pseudoref(refname, new_oid, old_oid, &err);
        } else {
                t = ref_store_transaction_begin(refs, &err);
                if (!t ||
-                   ref_transaction_update(t, refname, new_sha1, old_sha1,
+                   ref_transaction_update(t, refname, new_oid ? new_oid->hash : NULL,
+                                          old_oid ? old_oid->hash : NULL,
                                           flags, msg, &err) ||
                    ref_transaction_commit(t, &err)) {
                        ret = 1;
@@ -1039,12 +1034,12 @@ int refs_update_ref(struct ref_store *refs, const char *msg,
 }
 
 int update_ref(const char *msg, const char *refname,
-              const unsigned char *new_sha1,
-              const unsigned char *old_sha1,
+              const struct object_id *new_oid,
+              const struct object_id *old_oid,
               unsigned int flags, enum action_on_err onerr)
 {
-       return refs_update_ref(get_main_ref_store(), msg, refname, new_sha1,
-                              old_sha1, flags, onerr);
+       return refs_update_ref(get_main_ref_store(), msg, refname, new_oid,
+                              old_oid, flags, onerr);
 }
 
 char *shorten_unambiguous_ref(const char *refname, int strict)
@@ -1287,6 +1282,10 @@ struct ref_iterator *refs_ref_iterator_begin(
        if (trim)
                iter = prefix_ref_iterator_begin(iter, "", trim);
 
+       /* Sanity check for subclasses: */
+       if (!iter->ordered)
+               BUG("reference iterator is not ordered");
+
        return iter;
 }
 
@@ -1398,9 +1397,12 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
                                    unsigned char *sha1, int *flags)
 {
        static struct strbuf sb_refname = STRBUF_INIT;
+       struct object_id unused_oid;
        int unused_flags;
        int symref_count;
 
+       if (!sha1)
+               sha1 = unused_oid.hash;
        if (!flags)
                flags = &unused_flags;
 
@@ -1430,8 +1432,21 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
                if (refs_read_raw_ref(refs, refname,
                                      sha1, &sb_refname, &read_flags)) {
                        *flags |= read_flags;
-                       if (errno != ENOENT || (resolve_flags & RESOLVE_REF_READING))
+
+                       /* In reading mode, refs must eventually resolve */
+                       if (resolve_flags & RESOLVE_REF_READING)
                                return NULL;
+
+                       /*
+                        * Otherwise a missing ref is OK. But the files backend
+                        * may show errors besides ENOENT if there are
+                        * similarly-named refs.
+                        */
+                       if (errno != ENOENT &&
+                           errno != EISDIR &&
+                           errno != ENOTDIR)
+                               return NULL;
+
                        hashclr(sha1);
                        if (*flags & REF_BAD_NAME)
                                *flags |= REF_ISBROKEN;
@@ -1685,7 +1700,23 @@ int refs_pack_refs(struct ref_store *refs, unsigned int flags)
 int refs_peel_ref(struct ref_store *refs, const char *refname,
                  unsigned char *sha1)
 {
-       return refs->be->peel_ref(refs, refname, sha1);
+       int flag;
+       unsigned char base[20];
+
+       if (current_ref_iter && current_ref_iter->refname == refname) {
+               struct object_id peeled;
+
+               if (ref_iterator_peel(current_ref_iter, &peeled))
+                       return -1;
+               hashcpy(sha1, peeled.hash);
+               return 0;
+       }
+
+       if (refs_read_ref_full(refs, refname,
+                              RESOLVE_REF_READING, base, &flag))
+               return -1;
+
+       return peel_object(base, sha1);
 }
 
 int peel_ref(const char *refname, unsigned char *sha1)
@@ -2035,3 +2066,14 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
 {
        return refs_rename_ref(get_main_ref_store(), oldref, newref, logmsg);
 }
+
+int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
+                   const char *newref, const char *logmsg)
+{
+       return refs->be->copy_ref(refs, oldref, newref, logmsg);
+}
+
+int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg)
+{
+       return refs_copy_existing_ref(get_main_ref_store(), oldref, newref, logmsg);
+}