Merge branch 'mh/lockfile'
authorJunio C Hamano <gitster@pobox.com>
Tue, 14 Oct 2014 17:49:45 +0000 (10:49 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 14 Oct 2014 17:49:45 +0000 (10:49 -0700)
The lockfile API and its users have been cleaned up.

* mh/lockfile: (38 commits)
lockfile.h: extract new header file for the functions in lockfile.c
hold_locked_index(): move from lockfile.c to read-cache.c
hold_lock_file_for_append(): restore errno before returning
get_locked_file_path(): new function
lockfile.c: rename static functions
lockfile: rename LOCK_NODEREF to LOCK_NO_DEREF
commit_lock_file_to(): refactor a helper out of commit_lock_file()
trim_last_path_component(): replace last_path_elm()
resolve_symlink(): take a strbuf parameter
resolve_symlink(): use a strbuf for internal scratch space
lockfile: change lock_file::filename into a strbuf
commit_lock_file(): use a strbuf to manage temporary space
try_merge_strategy(): use a statically-allocated lock_file object
try_merge_strategy(): remove redundant lock_file allocation
struct lock_file: declare some fields volatile
lockfile: avoid transitory invalid states
git_config_set_multivar_in_file(): avoid call to rollback_lock_file()
dump_marks(): remove a redundant call to rollback_lock_file()
api-lockfile: document edge cases
commit_lock_file(): rollback lock file on failure to rename
...

1  2 
builtin/receive-pack.c
cache.h
config.c
lockfile.c
merge-recursive.c
sha1_file.c
diff --combined builtin/receive-pack.c
index a01ac2096a70fcfbe4f206cca1b06ded1a1daffc,10fa25d7b5632abd245bfbeaee56363552498c6c..f2f6c67359cb5941c2d57b6692ce1fbbc86edccc
@@@ -1,4 -1,5 +1,5 @@@
  #include "builtin.h"
+ #include "lockfile.h"
  #include "pack.h"
  #include "refs.h"
  #include "pkt-line.h"
@@@ -15,8 -16,6 +16,8 @@@
  #include "connected.h"
  #include "argv-array.h"
  #include "version.h"
 +#include "tag.h"
 +#include "gpg-interface.h"
  #include "sigchain.h"
  
  static const char receive_pack_usage[] = "git receive-pack <git-dir>";
@@@ -44,27 -43,11 +45,27 @@@ static int prefer_ofs_delta = 1
  static int auto_update_server_info;
  static int auto_gc = 1;
  static int fix_thin = 1;
 +static int stateless_rpc;
 +static const char *service_dir;
  static const char *head_name;
  static void *head_name_to_free;
  static int sent_capabilities;
  static int shallow_update;
  static const char *alt_shallow_file;
 +static struct strbuf push_cert = STRBUF_INIT;
 +static unsigned char push_cert_sha1[20];
 +static struct signature_check sigcheck;
 +static const char *push_cert_nonce;
 +static const char *cert_nonce_seed;
 +
 +static const char *NONCE_UNSOLICITED = "UNSOLICITED";
 +static const char *NONCE_BAD = "BAD";
 +static const char *NONCE_MISSING = "MISSING";
 +static const char *NONCE_OK = "OK";
 +static const char *NONCE_SLOP = "SLOP";
 +static const char *nonce_status;
 +static long nonce_stamp_slop;
 +static unsigned long nonce_stamp_slop_limit;
  
  static enum deny_action parse_deny_action(const char *var, const char *value)
  {
@@@ -148,14 -131,6 +149,14 @@@ static int receive_pack_config(const ch
                return 0;
        }
  
 +      if (strcmp(var, "receive.certnonceseed") == 0)
 +              return git_config_string(&cert_nonce_seed, var, value);
 +
 +      if (strcmp(var, "receive.certnonceslop") == 0) {
 +              nonce_stamp_slop_limit = git_config_ulong(var, value);
 +              return 0;
 +      }
 +
        return git_default_config(var, value, cb);
  }
  
@@@ -164,23 -139,15 +165,23 @@@ static void show_ref(const char *path, 
        if (ref_is_hidden(path))
                return;
  
 -      if (sent_capabilities)
 +      if (sent_capabilities) {
                packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
 -      else
 -              packet_write(1, "%s %s%c%s%s agent=%s\n",
 -                           sha1_to_hex(sha1), path, 0,
 -                           " report-status delete-refs side-band-64k quiet",
 -                           prefer_ofs_delta ? " ofs-delta" : "",
 -                           git_user_agent_sanitized());
 -      sent_capabilities = 1;
 +      } else {
 +              struct strbuf cap = STRBUF_INIT;
 +
 +              strbuf_addstr(&cap,
 +                            "report-status delete-refs side-band-64k quiet");
 +              if (prefer_ofs_delta)
 +                      strbuf_addstr(&cap, " ofs-delta");
 +              if (push_cert_nonce)
 +                      strbuf_addf(&cap, " push-cert=%s", push_cert_nonce);
 +              strbuf_addf(&cap, " agent=%s", git_user_agent_sanitized());
 +              packet_write(1, "%s %s%c%s\n",
 +                           sha1_to_hex(sha1), path, 0, cap.buf);
 +              strbuf_release(&cap);
 +              sent_capabilities = 1;
 +      }
  }
  
  static int show_ref_cb(const char *path, const unsigned char *sha1, int flag, void *unused)
@@@ -287,222 -254,6 +288,222 @@@ static int copy_to_sideband(int in, in
        return 0;
  }
  
 +#define HMAC_BLOCK_SIZE 64
 +
 +static void hmac_sha1(unsigned char *out,
 +                    const char *key_in, size_t key_len,
 +                    const char *text, size_t text_len)
 +{
 +      unsigned char key[HMAC_BLOCK_SIZE];
 +      unsigned char k_ipad[HMAC_BLOCK_SIZE];
 +      unsigned char k_opad[HMAC_BLOCK_SIZE];
 +      int i;
 +      git_SHA_CTX ctx;
 +
 +      /* RFC 2104 2. (1) */
 +      memset(key, '\0', HMAC_BLOCK_SIZE);
 +      if (HMAC_BLOCK_SIZE < key_len) {
 +              git_SHA1_Init(&ctx);
 +              git_SHA1_Update(&ctx, key_in, key_len);
 +              git_SHA1_Final(key, &ctx);
 +      } else {
 +              memcpy(key, key_in, key_len);
 +      }
 +
 +      /* RFC 2104 2. (2) & (5) */
 +      for (i = 0; i < sizeof(key); i++) {
 +              k_ipad[i] = key[i] ^ 0x36;
 +              k_opad[i] = key[i] ^ 0x5c;
 +      }
 +
 +      /* RFC 2104 2. (3) & (4) */
 +      git_SHA1_Init(&ctx);
 +      git_SHA1_Update(&ctx, k_ipad, sizeof(k_ipad));
 +      git_SHA1_Update(&ctx, text, text_len);
 +      git_SHA1_Final(out, &ctx);
 +
 +      /* RFC 2104 2. (6) & (7) */
 +      git_SHA1_Init(&ctx);
 +      git_SHA1_Update(&ctx, k_opad, sizeof(k_opad));
 +      git_SHA1_Update(&ctx, out, 20);
 +      git_SHA1_Final(out, &ctx);
 +}
 +
 +static char *prepare_push_cert_nonce(const char *path, unsigned long stamp)
 +{
 +      struct strbuf buf = STRBUF_INIT;
 +      unsigned char sha1[20];
 +
 +      strbuf_addf(&buf, "%s:%lu", path, stamp);
 +      hmac_sha1(sha1, buf.buf, buf.len, cert_nonce_seed, strlen(cert_nonce_seed));;
 +      strbuf_release(&buf);
 +
 +      /* RFC 2104 5. HMAC-SHA1-80 */
 +      strbuf_addf(&buf, "%lu-%.*s", stamp, 20, sha1_to_hex(sha1));
 +      return strbuf_detach(&buf, NULL);
 +}
 +
 +/*
 + * NEEDSWORK: reuse find_commit_header() from jk/commit-author-parsing
 + * after dropping "_commit" from its name and possibly moving it out
 + * of commit.c
 + */
 +static char *find_header(const char *msg, size_t len, const char *key)
 +{
 +      int key_len = strlen(key);
 +      const char *line = msg;
 +
 +      while (line && line < msg + len) {
 +              const char *eol = strchrnul(line, '\n');
 +
 +              if ((msg + len <= eol) || line == eol)
 +                      return NULL;
 +              if (line + key_len < eol &&
 +                  !memcmp(line, key, key_len) && line[key_len] == ' ') {
 +                      int offset = key_len + 1;
 +                      return xmemdupz(line + offset, (eol - line) - offset);
 +              }
 +              line = *eol ? eol + 1 : NULL;
 +      }
 +      return NULL;
 +}
 +
 +static const char *check_nonce(const char *buf, size_t len)
 +{
 +      char *nonce = find_header(buf, len, "nonce");
 +      unsigned long stamp, ostamp;
 +      char *bohmac, *expect = NULL;
 +      const char *retval = NONCE_BAD;
 +
 +      if (!nonce) {
 +              retval = NONCE_MISSING;
 +              goto leave;
 +      } else if (!push_cert_nonce) {
 +              retval = NONCE_UNSOLICITED;
 +              goto leave;
 +      } else if (!strcmp(push_cert_nonce, nonce)) {
 +              retval = NONCE_OK;
 +              goto leave;
 +      }
 +
 +      if (!stateless_rpc) {
 +              /* returned nonce MUST match what we gave out earlier */
 +              retval = NONCE_BAD;
 +              goto leave;
 +      }
 +
 +      /*
 +       * In stateless mode, we may be receiving a nonce issued by
 +       * another instance of the server that serving the same
 +       * repository, and the timestamps may not match, but the
 +       * nonce-seed and dir should match, so we can recompute and
 +       * report the time slop.
 +       *
 +       * In addition, when a nonce issued by another instance has
 +       * timestamp within receive.certnonceslop seconds, we pretend
 +       * as if we issued that nonce when reporting to the hook.
 +       */
 +
 +      /* nonce is concat(<seconds-since-epoch>, "-", <hmac>) */
 +      if (*nonce <= '0' || '9' < *nonce) {
 +              retval = NONCE_BAD;
 +              goto leave;
 +      }
 +      stamp = strtoul(nonce, &bohmac, 10);
 +      if (bohmac == nonce || bohmac[0] != '-') {
 +              retval = NONCE_BAD;
 +              goto leave;
 +      }
 +
 +      expect = prepare_push_cert_nonce(service_dir, stamp);
 +      if (strcmp(expect, nonce)) {
 +              /* Not what we would have signed earlier */
 +              retval = NONCE_BAD;
 +              goto leave;
 +      }
 +
 +      /*
 +       * By how many seconds is this nonce stale?  Negative value
 +       * would mean it was issued by another server with its clock
 +       * skewed in the future.
 +       */
 +      ostamp = strtoul(push_cert_nonce, NULL, 10);
 +      nonce_stamp_slop = (long)ostamp - (long)stamp;
 +
 +      if (nonce_stamp_slop_limit &&
 +          abs(nonce_stamp_slop) <= nonce_stamp_slop_limit) {
 +              /*
 +               * Pretend as if the received nonce (which passes the
 +               * HMAC check, so it is not a forged by third-party)
 +               * is what we issued.
 +               */
 +              free((void *)push_cert_nonce);
 +              push_cert_nonce = xstrdup(nonce);
 +              retval = NONCE_OK;
 +      } else {
 +              retval = NONCE_SLOP;
 +      }
 +
 +leave:
 +      free(nonce);
 +      free(expect);
 +      return retval;
 +}
 +
 +static void prepare_push_cert_sha1(struct child_process *proc)
 +{
 +      static int already_done;
 +      struct argv_array env = ARGV_ARRAY_INIT;
 +
 +      if (!push_cert.len)
 +              return;
 +
 +      if (!already_done) {
 +              struct strbuf gpg_output = STRBUF_INIT;
 +              struct strbuf gpg_status = STRBUF_INIT;
 +              int bogs /* beginning_of_gpg_sig */;
 +
 +              already_done = 1;
 +              if (write_sha1_file(push_cert.buf, push_cert.len, "blob", push_cert_sha1))
 +                      hashclr(push_cert_sha1);
 +
 +              memset(&sigcheck, '\0', sizeof(sigcheck));
 +              sigcheck.result = 'N';
 +
 +              bogs = parse_signature(push_cert.buf, push_cert.len);
 +              if (verify_signed_buffer(push_cert.buf, bogs,
 +                                       push_cert.buf + bogs, push_cert.len - bogs,
 +                                       &gpg_output, &gpg_status) < 0) {
 +                      ; /* error running gpg */
 +              } else {
 +                      sigcheck.payload = push_cert.buf;
 +                      sigcheck.gpg_output = gpg_output.buf;
 +                      sigcheck.gpg_status = gpg_status.buf;
 +                      parse_gpg_output(&sigcheck);
 +              }
 +
 +              strbuf_release(&gpg_output);
 +              strbuf_release(&gpg_status);
 +              nonce_status = check_nonce(push_cert.buf, bogs);
 +      }
 +      if (!is_null_sha1(push_cert_sha1)) {
 +              argv_array_pushf(&env, "GIT_PUSH_CERT=%s", sha1_to_hex(push_cert_sha1));
 +              argv_array_pushf(&env, "GIT_PUSH_CERT_SIGNER=%s",
 +                               sigcheck.signer ? sigcheck.signer : "");
 +              argv_array_pushf(&env, "GIT_PUSH_CERT_KEY=%s",
 +                               sigcheck.key ? sigcheck.key : "");
 +              argv_array_pushf(&env, "GIT_PUSH_CERT_STATUS=%c", sigcheck.result);
 +              if (push_cert_nonce) {
 +                      argv_array_pushf(&env, "GIT_PUSH_CERT_NONCE=%s", push_cert_nonce);
 +                      argv_array_pushf(&env, "GIT_PUSH_CERT_NONCE_STATUS=%s", nonce_status);
 +                      if (nonce_status == NONCE_SLOP)
 +                              argv_array_pushf(&env, "GIT_PUSH_CERT_NONCE_SLOP=%ld",
 +                                               nonce_stamp_slop);
 +              }
 +              proc->env = env.argv;
 +      }
 +}
 +
  typedef int (*feed_fn)(void *, const char **, size_t *);
  static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_state)
  {
        proc.in = -1;
        proc.stdout_to_stderr = 1;
  
 +      prepare_push_cert_sha1(&proc);
 +
        if (use_sideband) {
                memset(&muxer, 0, sizeof(muxer));
                muxer.proc = copy_to_sideband;
@@@ -1093,79 -842,40 +1094,79 @@@ static void execute_commands(struct com
                      "the reported refs above");
  }
  
 +static struct command **queue_command(struct command **tail,
 +                                    const char *line,
 +                                    int linelen)
 +{
 +      unsigned char old_sha1[20], new_sha1[20];
 +      struct command *cmd;
 +      const char *refname;
 +      int reflen;
 +
 +      if (linelen < 83 ||
 +          line[40] != ' ' ||
 +          line[81] != ' ' ||
 +          get_sha1_hex(line, old_sha1) ||
 +          get_sha1_hex(line + 41, new_sha1))
 +              die("protocol error: expected old/new/ref, got '%s'", line);
 +
 +      refname = line + 82;
 +      reflen = linelen - 82;
 +      cmd = xcalloc(1, sizeof(struct command) + reflen + 1);
 +      hashcpy(cmd->old_sha1, old_sha1);
 +      hashcpy(cmd->new_sha1, new_sha1);
 +      memcpy(cmd->ref_name, refname, reflen);
 +      cmd->ref_name[reflen] = '\0';
 +      *tail = cmd;
 +      return &cmd->next;
 +}
 +
 +static void queue_commands_from_cert(struct command **tail,
 +                                   struct strbuf *push_cert)
 +{
 +      const char *boc, *eoc;
 +
 +      if (*tail)
 +              die("protocol error: got both push certificate and unsigned commands");
 +
 +      boc = strstr(push_cert->buf, "\n\n");
 +      if (!boc)
 +              die("malformed push certificate %.*s", 100, push_cert->buf);
 +      else
 +              boc += 2;
 +      eoc = push_cert->buf + parse_signature(push_cert->buf, push_cert->len);
 +
 +      while (boc < eoc) {
 +              const char *eol = memchr(boc, '\n', eoc - boc);
 +              tail = queue_command(tail, boc, eol ? eol - boc : eoc - eol);
 +              boc = eol ? eol + 1 : eoc;
 +      }
 +}
 +
  static struct command *read_head_info(struct sha1_array *shallow)
  {
        struct command *commands = NULL;
        struct command **p = &commands;
        for (;;) {
                char *line;
 -              unsigned char old_sha1[20], new_sha1[20];
 -              struct command *cmd;
 -              char *refname;
 -              int len, reflen;
 +              int len, linelen;
  
                line = packet_read_line(0, &len);
                if (!line)
                        break;
  
                if (len == 48 && starts_with(line, "shallow ")) {
 -                      if (get_sha1_hex(line + 8, old_sha1))
 -                              die("protocol error: expected shallow sha, got '%s'", line + 8);
 -                      sha1_array_append(shallow, old_sha1);
 +                      unsigned char sha1[20];
 +                      if (get_sha1_hex(line + 8, sha1))
 +                              die("protocol error: expected shallow sha, got '%s'",
 +                                  line + 8);
 +                      sha1_array_append(shallow, sha1);
                        continue;
                }
  
 -              if (len < 83 ||
 -                  line[40] != ' ' ||
 -                  line[81] != ' ' ||
 -                  get_sha1_hex(line, old_sha1) ||
 -                  get_sha1_hex(line + 41, new_sha1))
 -                      die("protocol error: expected old/new/ref, got '%s'",
 -                          line);
 -
 -              refname = line + 82;
 -              reflen = strlen(refname);
 -              if (reflen + 82 < len) {
 -                      const char *feature_list = refname + reflen + 1;
 +              linelen = strlen(line);
 +              if (linelen < len) {
 +                      const char *feature_list = line + linelen + 1;
                        if (parse_feature_request(feature_list, "report-status"))
                                report_status = 1;
                        if (parse_feature_request(feature_list, "side-band-64k"))
                        if (parse_feature_request(feature_list, "quiet"))
                                quiet = 1;
                }
 -              cmd = xcalloc(1, sizeof(struct command) + len - 80);
 -              hashcpy(cmd->old_sha1, old_sha1);
 -              hashcpy(cmd->new_sha1, new_sha1);
 -              memcpy(cmd->ref_name, line + 82, len - 81);
 -              *p = cmd;
 -              p = &cmd->next;
 +
 +              if (!strcmp(line, "push-cert")) {
 +                      int true_flush = 0;
 +                      char certbuf[1024];
 +
 +                      for (;;) {
 +                              len = packet_read(0, NULL, NULL,
 +                                                certbuf, sizeof(certbuf), 0);
 +                              if (!len) {
 +                                      true_flush = 1;
 +                                      break;
 +                              }
 +                              if (!strcmp(certbuf, "push-cert-end\n"))
 +                                      break; /* end of cert */
 +                              strbuf_addstr(&push_cert, certbuf);
 +                      }
 +
 +                      if (true_flush)
 +                              break;
 +                      continue;
 +              }
 +
 +              p = queue_command(p, line, linelen);
        }
 +
 +      if (push_cert.len)
 +              queue_commands_from_cert(p, &push_cert);
 +
        return commands;
  }
  
@@@ -1441,7 -1130,9 +1442,7 @@@ static int delete_only(struct command *
  int cmd_receive_pack(int argc, const char **argv, const char *prefix)
  {
        int advertise_refs = 0;
 -      int stateless_rpc = 0;
        int i;
 -      const char *dir = NULL;
        struct command *commands;
        struct sha1_array shallow = SHA1_ARRAY_INIT;
        struct sha1_array ref = SHA1_ARRAY_INIT;
  
                        usage(receive_pack_usage);
                }
 -              if (dir)
 +              if (service_dir)
                        usage(receive_pack_usage);
 -              dir = arg;
 +              service_dir = arg;
        }
 -      if (!dir)
 +      if (!service_dir)
                usage(receive_pack_usage);
  
        setup_path();
  
 -      if (!enter_repo(dir, 0))
 -              die("'%s' does not appear to be a git repository", dir);
 +      if (!enter_repo(service_dir, 0))
 +              die("'%s' does not appear to be a git repository", service_dir);
  
        git_config(receive_pack_config, NULL);
 +      if (cert_nonce_seed)
 +              push_cert_nonce = prepare_push_cert_nonce(service_dir, time(NULL));
  
        if (0 <= transfer_unpack_limit)
                unpack_limit = transfer_unpack_limit;
                packet_flush(1);
        sha1_array_clear(&shallow);
        sha1_array_clear(&ref);
 +      free((void *)push_cert_nonce);
        return 0;
  }
diff --combined cache.h
index 3e6a914dba74419c131f75b86459b4b7cea560ea,b71ceb2d8de07f77a6385025c5d154acf529ada5..5b8606581563584c2ff274089ad16429a81a1742
+++ b/cache.h
@@@ -570,29 -570,11 +570,11 @@@ extern void fill_stat_cache_info(struc
  #define REFRESH_IN_PORCELAIN  0x0020  /* user friendly output, not "needs update" */
  extern int refresh_index(struct index_state *, unsigned int flags, const struct pathspec *pathspec, char *seen, const char *header_msg);
  
- struct lock_file {
-       struct lock_file *next;
-       int fd;
-       pid_t owner;
-       char on_list;
-       char filename[PATH_MAX];
- };
- #define LOCK_DIE_ON_ERROR 1
- #define LOCK_NODEREF 2
- extern int unable_to_lock_error(const char *path, int err);
- extern void unable_to_lock_message(const char *path, int err,
-                                  struct strbuf *buf);
- extern NORETURN void unable_to_lock_index_die(const char *path, int err);
- extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
- extern int hold_lock_file_for_append(struct lock_file *, const char *path, int);
- extern int commit_lock_file(struct lock_file *);
- extern int reopen_lock_file(struct lock_file *);
  extern void update_index_if_able(struct index_state *, struct lock_file *);
  
  extern int hold_locked_index(struct lock_file *, int);
  extern void set_alternate_index_output(const char *);
- extern int close_lock_file(struct lock_file *);
- extern void rollback_lock_file(struct lock_file *);
  extern int delete_ref(const char *, const unsigned char *sha1, int delopt);
  
  /* Environment bits from configuration mechanism */
@@@ -1324,7 -1306,6 +1306,7 @@@ extern int git_config_rename_section_in
  extern const char *git_etc_gitconfig(void);
  extern int check_repository_format_version(const char *var, const char *value, void *cb);
  extern int git_env_bool(const char *, int);
 +extern unsigned long git_env_ulong(const char *, unsigned long);
  extern int git_config_system(void);
  extern int config_error_nonbool(const char *);
  #if defined(__GNUC__)
diff --combined config.c
index 039647d247e0ac1e3a523800bbb90c86d89354de,c31d4d2486c7f11ae31c841ed95157426dbf11c0..15a298357796ffa80f7fb2258e55cf95be0b8895
+++ b/config.c
@@@ -6,6 -6,7 +6,7 @@@
   *
   */
  #include "cache.h"
+ #include "lockfile.h"
  #include "exec_cmd.h"
  #include "strbuf.h"
  #include "quote.h"
@@@ -1139,28 -1140,12 +1140,28 @@@ const char *git_etc_gitconfig(void
        return system_wide;
  }
  
 +/*
 + * Parse environment variable 'k' as a boolean (in various
 + * possible spellings); if missing, use the default value 'def'.
 + */
  int git_env_bool(const char *k, int def)
  {
        const char *v = getenv(k);
        return v ? git_config_bool(k, v) : def;
  }
  
 +/*
 + * Parse environment variable 'k' as ulong with possibly a unit
 + * suffix; if missing, use the default value 'val'.
 + */
 +unsigned long git_env_ulong(const char *k, unsigned long val)
 +{
 +      const char *v = getenv(k);
 +      if (v && !git_parse_ulong(v, &val))
 +              die("failed to parse %s", k);
 +      return val;
 +}
 +
  int git_config_system(void)
  {
        return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0);
@@@ -2040,9 -2025,9 +2041,9 @@@ int git_config_set_multivar_in_file(con
                        MAP_PRIVATE, in_fd, 0);
                close(in_fd);
  
-               if (chmod(lock->filename, st.st_mode & 07777) < 0) {
+               if (chmod(lock->filename.buf, st.st_mode & 07777) < 0) {
                        error("chmod on %s failed: %s",
-                               lock->filename, strerror(errno));
+                               lock->filename.buf, strerror(errno));
                        ret = CONFIG_NO_WRITE;
                        goto out_free;
                }
        if (commit_lock_file(lock) < 0) {
                error("could not commit config file %s", config_filename);
                ret = CONFIG_NO_WRITE;
+               lock = NULL;
                goto out_free;
        }
  
@@@ -2121,7 -2107,7 +2123,7 @@@ out_free
        return ret;
  
  write_err_out:
-       ret = write_error(lock->filename);
+       ret = write_error(lock->filename.buf);
        goto out_free;
  
  }
@@@ -2222,9 -2208,9 +2224,9 @@@ int git_config_rename_section_in_file(c
  
        fstat(fileno(config_file), &st);
  
-       if (chmod(lock->filename, st.st_mode & 07777) < 0) {
+       if (chmod(lock->filename.buf, st.st_mode & 07777) < 0) {
                ret = error("chmod on %s failed: %s",
-                               lock->filename, strerror(errno));
+                               lock->filename.buf, strerror(errno));
                goto out;
        }
  
                                }
                                store.baselen = strlen(new_name);
                                if (!store_write_section(out_fd, new_name)) {
-                                       ret = write_error(lock->filename);
+                                       ret = write_error(lock->filename.buf);
                                        goto out;
                                }
                                /*
                        continue;
                length = strlen(output);
                if (write_in_full(out_fd, output, length) != length) {
-                       ret = write_error(lock->filename);
+                       ret = write_error(lock->filename.buf);
                        goto out;
                }
        }
diff --combined lockfile.c
index d34a96df4f859feeaa7597abba374128ff9dc598,d27e61cafcfd2a8277e67edddaf41c29065babee..7bfec4b773bc710b474677b808c4bdacf53af91b
@@@ -2,59 -2,52 +2,52 @@@
   * Copyright (c) 2005, Junio C Hamano
   */
  #include "cache.h"
+ #include "lockfile.h"
  #include "sigchain.h"
  
- static struct lock_file *lock_file_list;
+ static struct lock_file *volatile lock_file_list;
  
- static void remove_lock_file(void)
+ static void remove_lock_files(void)
  {
        pid_t me = getpid();
  
        while (lock_file_list) {
-               if (lock_file_list->owner == me &&
-                   lock_file_list->filename[0]) {
-                       if (lock_file_list->fd >= 0)
-                               close(lock_file_list->fd);
-                       unlink_or_warn(lock_file_list->filename);
-               }
+               if (lock_file_list->owner == me)
+                       rollback_lock_file(lock_file_list);
                lock_file_list = lock_file_list->next;
        }
  }
  
- static void remove_lock_file_on_signal(int signo)
+ static void remove_lock_files_on_signal(int signo)
  {
-       remove_lock_file();
+       remove_lock_files();
        sigchain_pop(signo);
        raise(signo);
  }
  
  /*
-  * p = absolute or relative path name
+  * path = absolute or relative path name
   *
-  * Return a pointer into p showing the beginning of the last path name
-  * element.  If p is empty or the root directory ("/"), just return p.
+  * Remove the last path name element from path (leaving the preceding
+  * "/", if any).  If path is empty or the root directory ("/"), set
+  * path to the empty string.
   */
- static char *last_path_elm(char *p)
+ static void trim_last_path_component(struct strbuf *path)
  {
-       /* r starts pointing to null at the end of the string */
-       char *r = strchr(p, '\0');
-       if (r == p)
-               return p; /* just return empty string */
-       r--; /* back up to last non-null character */
+       int i = path->len;
  
        /* back up past trailing slashes, if any */
-       while (r > p && *r == '/')
-               r--;
+       while (i && path->buf[i - 1] == '/')
+               i--;
  
        /*
-        * then go backwards until I hit a slash, or the beginning of
-        * the string
+        * then go backwards until a slash, or the beginning of the
+        * string
         */
-       while (r > p && *(r-1) != '/')
-               r--;
-       return r;
+       while (i && path->buf[i - 1] != '/')
+               i--;
+       strbuf_setlen(path, i);
  }
  
  
  #define MAXDEPTH 5
  
  /*
-  * p = path that may be a symlink
-  * s = full size of p
-  *
-  * If p is a symlink, attempt to overwrite p with a path to the real
-  * file or directory (which may or may not exist), following a chain of
-  * symlinks if necessary.  Otherwise, leave p unmodified.
+  * path contains a path that might be a symlink.
   *
-  * This is a best-effort routine.  If an error occurs, p will either be
-  * left unmodified or will name a different symlink in a symlink chain
-  * that started with p's initial contents.
+  * If path is a symlink, attempt to overwrite it with a path to the
+  * real file or directory (which may or may not exist), following a
+  * chain of symlinks if necessary.  Otherwise, leave path unmodified.
   *
-  * Always returns p.
+  * This is a best-effort routine.  If an error occurs, path will
+  * either be left unmodified or will name a different symlink in a
+  * symlink chain that started with the original path.
   */
- static char *resolve_symlink(char *p, size_t s)
+ static void resolve_symlink(struct strbuf *path)
  {
        int depth = MAXDEPTH;
+       static struct strbuf link = STRBUF_INIT;
  
        while (depth--) {
-               char link[PATH_MAX];
-               int link_len = readlink(p, link, sizeof(link));
-               if (link_len < 0) {
-                       /* not a symlink anymore */
-                       return p;
-               }
-               else if (link_len < sizeof(link))
-                       /* readlink() never null-terminates */
-                       link[link_len] = '\0';
-               else {
-                       warning("%s: symlink too long", p);
-                       return p;
-               }
+               if (strbuf_readlink(&link, path->buf, path->len) < 0)
+                       break;
  
-               if (is_absolute_path(link)) {
+               if (is_absolute_path(link.buf))
                        /* absolute path simply replaces p */
-                       if (link_len < s)
-                               strcpy(p, link);
-                       else {
-                               warning("%s: symlink too long", p);
-                               return p;
-                       }
-               } else {
+                       strbuf_reset(path);
+               else
                        /*
-                        * link is a relative path, so I must replace the
+                        * link is a relative path, so replace the
                         * last element of p with it.
                         */
-                       char *r = (char *)last_path_elm(p);
-                       if (r - p + link_len < s)
-                               strcpy(r, link);
-                       else {
-                               warning("%s: symlink too long", p);
-                               return p;
-                       }
-               }
+                       trim_last_path_component(path);
+               strbuf_addbuf(path, &link);
        }
-       return p;
+       strbuf_reset(&link);
  }
  
  /* Make sure errno contains a meaningful value on error */
  static int lock_file(struct lock_file *lk, const char *path, int flags)
  {
-       /*
-        * subtract 5 from size to make sure there's room for adding
-        * ".lock" for the lock file name
-        */
-       static const size_t max_path_len = sizeof(lk->filename) - 5;
+       size_t pathlen = strlen(path);
  
-       if (strlen(path) >= max_path_len) {
-               errno = ENAMETOOLONG;
+       if (!lock_file_list) {
+               /* One-time initialization */
+               sigchain_push_common(remove_lock_files_on_signal);
+               atexit(remove_lock_files);
+       }
+       if (lk->active)
+               die("BUG: cannot lock_file(\"%s\") using active struct lock_file",
+                   path);
+       if (!lk->on_list) {
+               /* Initialize *lk and add it to lock_file_list: */
+               lk->fd = -1;
+               lk->active = 0;
+               lk->owner = 0;
+               strbuf_init(&lk->filename, pathlen + LOCK_SUFFIX_LEN);
+               lk->next = lock_file_list;
+               lock_file_list = lk;
+               lk->on_list = 1;
+       } else if (lk->filename.len) {
+               /* This shouldn't happen, but better safe than sorry. */
+               die("BUG: lock_file(\"%s\") called with improperly-reset lock_file object",
+                   path);
+       }
+       strbuf_add(&lk->filename, path, pathlen);
+       if (!(flags & LOCK_NO_DEREF))
+               resolve_symlink(&lk->filename);
+       strbuf_addstr(&lk->filename, LOCK_SUFFIX);
+       lk->fd = open(lk->filename.buf, O_RDWR | O_CREAT | O_EXCL, 0666);
+       if (lk->fd < 0) {
+               strbuf_reset(&lk->filename);
                return -1;
        }
-       strcpy(lk->filename, path);
-       if (!(flags & LOCK_NODEREF))
-               resolve_symlink(lk->filename, max_path_len);
-       strcat(lk->filename, ".lock");
-       lk->fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666);
-       if (0 <= lk->fd) {
-               if (!lock_file_list) {
-                       sigchain_push_common(remove_lock_file_on_signal);
-                       atexit(remove_lock_file);
-               }
-               lk->owner = getpid();
-               if (!lk->on_list) {
-                       lk->next = lock_file_list;
-                       lock_file_list = lk;
-                       lk->on_list = 1;
-               }
-               if (adjust_shared_perm(lk->filename)) {
-                       int save_errno = errno;
-                       error("cannot fix permission bits on %s",
-                             lk->filename);
-                       errno = save_errno;
-                       return -1;
-               }
+       lk->owner = getpid();
+       lk->active = 1;
+       if (adjust_shared_perm(lk->filename.buf)) {
+               int save_errno = errno;
+               error("cannot fix permission bits on %s", lk->filename.buf);
+               rollback_lock_file(lk);
+               errno = save_errno;
+               return -1;
        }
-       else
-               lk->filename[0] = 0;
        return lk->fd;
  }
  
@@@ -185,7 -162,7 +162,7 @@@ int unable_to_lock_error(const char *pa
        return -1;
  }
  
- NORETURN void unable_to_lock_index_die(const char *path, int err)
+ NORETURN void unable_to_lock_die(const char *path, int err)
  {
        struct strbuf buf = STRBUF_INIT;
  
@@@ -198,7 -175,7 +175,7 @@@ int hold_lock_file_for_update(struct lo
  {
        int fd = lock_file(lk, path, flags);
        if (fd < 0 && (flags & LOCK_DIE_ON_ERROR))
-               unable_to_lock_index_die(path, errno);
+               unable_to_lock_die(path, errno);
        return fd;
  }
  
@@@ -209,76 -186,118 +186,121 @@@ int hold_lock_file_for_append(struct lo
        fd = lock_file(lk, path, flags);
        if (fd < 0) {
                if (flags & LOCK_DIE_ON_ERROR)
-                       unable_to_lock_index_die(path, errno);
+                       unable_to_lock_die(path, errno);
                return fd;
        }
  
        orig_fd = open(path, O_RDONLY);
        if (orig_fd < 0) {
                if (errno != ENOENT) {
+                       int save_errno = errno;
                        if (flags & LOCK_DIE_ON_ERROR)
                                die("cannot open '%s' for copying", path);
-                       close(fd);
-                       return error("cannot open '%s' for copying", path);
+                       rollback_lock_file(lk);
+                       error("cannot open '%s' for copying", path);
+                       errno = save_errno;
+                       return -1;
                }
        } else if (copy_fd(orig_fd, fd)) {
+               int save_errno = errno;
                if (flags & LOCK_DIE_ON_ERROR)
                        exit(128);
-               close(fd);
 +              close(orig_fd);
+               rollback_lock_file(lk);
+               errno = save_errno;
                return -1;
 +      } else {
 +              close(orig_fd);
        }
        return fd;
  }
  
+ char *get_locked_file_path(struct lock_file *lk)
+ {
+       if (!lk->active)
+               die("BUG: get_locked_file_path() called for unlocked object");
+       if (lk->filename.len <= LOCK_SUFFIX_LEN)
+               die("BUG: get_locked_file_path() called for malformed lock object");
+       return xmemdupz(lk->filename.buf, lk->filename.len - LOCK_SUFFIX_LEN);
+ }
  int close_lock_file(struct lock_file *lk)
  {
        int fd = lk->fd;
+       if (fd < 0)
+               return 0;
        lk->fd = -1;
-       return close(fd);
+       if (close(fd)) {
+               int save_errno = errno;
+               rollback_lock_file(lk);
+               errno = save_errno;
+               return -1;
+       }
+       return 0;
  }
  
  int reopen_lock_file(struct lock_file *lk)
  {
        if (0 <= lk->fd)
                die(_("BUG: reopen a lockfile that is still open"));
-       if (!lk->filename[0])
+       if (!lk->active)
                die(_("BUG: reopen a lockfile that has been committed"));
-       lk->fd = open(lk->filename, O_WRONLY);
+       lk->fd = open(lk->filename.buf, O_WRONLY);
        return lk->fd;
  }
  
- int commit_lock_file(struct lock_file *lk)
+ int commit_lock_file_to(struct lock_file *lk, const char *path)
  {
-       char result_file[PATH_MAX];
-       size_t i;
-       if (lk->fd >= 0 && close_lock_file(lk))
+       if (!lk->active)
+               die("BUG: attempt to commit unlocked object to \"%s\"", path);
+       if (close_lock_file(lk))
                return -1;
-       strcpy(result_file, lk->filename);
-       i = strlen(result_file) - 5; /* .lock */
-       result_file[i] = 0;
-       if (rename(lk->filename, result_file))
+       if (rename(lk->filename.buf, path)) {
+               int save_errno = errno;
+               rollback_lock_file(lk);
+               errno = save_errno;
                return -1;
-       lk->filename[0] = 0;
+       }
+       lk->active = 0;
+       strbuf_reset(&lk->filename);
        return 0;
  }
  
- int hold_locked_index(struct lock_file *lk, int die_on_error)
+ int commit_lock_file(struct lock_file *lk)
  {
-       return hold_lock_file_for_update(lk, get_index_file(),
-                                        die_on_error
-                                        ? LOCK_DIE_ON_ERROR
-                                        : 0);
+       static struct strbuf result_file = STRBUF_INIT;
+       int err;
+       if (!lk->active)
+               die("BUG: attempt to commit unlocked object");
+       if (lk->filename.len <= LOCK_SUFFIX_LEN ||
+           strcmp(lk->filename.buf + lk->filename.len - LOCK_SUFFIX_LEN, LOCK_SUFFIX))
+               die("BUG: lockfile filename corrupt");
+       /* remove ".lock": */
+       strbuf_add(&result_file, lk->filename.buf,
+                  lk->filename.len - LOCK_SUFFIX_LEN);
+       err = commit_lock_file_to(lk, result_file.buf);
+       strbuf_reset(&result_file);
+       return err;
  }
  
  void rollback_lock_file(struct lock_file *lk)
  {
-       if (lk->filename[0]) {
-               if (lk->fd >= 0)
-                       close(lk->fd);
-               unlink_or_warn(lk->filename);
+       if (!lk->active)
+               return;
+       if (!close_lock_file(lk)) {
+               unlink_or_warn(lk->filename.buf);
+               lk->active = 0;
+               strbuf_reset(&lk->filename);
        }
-       lk->filename[0] = 0;
  }
diff --combined merge-recursive.c
index 9fc71a2391bff6c718cab39865371d2b43bf15a0,4b0884b7bbdf177d6cfa2d07d3888cd212ec8a62..fdb7d0f10ba471bcdff26dc87851d7df34fdc971
@@@ -3,8 -3,9 +3,9 @@@
   * Fredrik Kuivinen.
   * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006
   */
 -#include "advice.h"
  #include "cache.h"
 +#include "advice.h"
+ #include "lockfile.h"
  #include "cache-tree.h"
  #include "commit.h"
  #include "blob.h"
diff --combined sha1_file.c
index 6f18c22ab186ba2214b6bedcec0cddb92e750c51,46ff273e272b8253c3349a64a47083ca7da84fa3..83f77f01b6370589fb90c72f111b4e6e381f9189
@@@ -8,6 -8,7 +8,7 @@@
   */
  #include "cache.h"
  #include "string-list.h"
+ #include "lockfile.h"
  #include "delta.h"
  #include "pack.h"
  #include "blob.h"
@@@ -663,26 -664,10 +664,26 @@@ void release_pack_memory(size_t need
                ; /* nothing */
  }
  
 +static void mmap_limit_check(size_t length)
 +{
 +      static size_t limit = 0;
 +      if (!limit) {
 +              limit = git_env_ulong("GIT_MMAP_LIMIT", 0);
 +              if (!limit)
 +                      limit = SIZE_MAX;
 +      }
 +      if (length > limit)
 +              die("attempting to mmap %"PRIuMAX" over limit %"PRIuMAX,
 +                  (uintmax_t)length, (uintmax_t)limit);
 +}
 +
  void *xmmap(void *start, size_t length,
        int prot, int flags, int fd, off_t offset)
  {
 -      void *ret = mmap(start, length, prot, flags, fd, offset);
 +      void *ret;
 +
 +      mmap_limit_check(length);
 +      ret = mmap(start, length, prot, flags, fd, offset);
        if (ret == MAP_FAILED) {
                if (!length)
                        return NULL;
@@@ -3092,29 -3077,6 +3093,29 @@@ static int index_mem(unsigned char *sha
        return ret;
  }
  
 +static int index_stream_convert_blob(unsigned char *sha1, int fd,
 +                                   const char *path, unsigned flags)
 +{
 +      int ret;
 +      const int write_object = flags & HASH_WRITE_OBJECT;
 +      struct strbuf sbuf = STRBUF_INIT;
 +
 +      assert(path);
 +      assert(would_convert_to_git_filter_fd(path));
 +
 +      convert_to_git_filter_fd(path, fd, &sbuf,
 +                               write_object ? safe_crlf : SAFE_CRLF_FALSE);
 +
 +      if (write_object)
 +              ret = write_sha1_file(sbuf.buf, sbuf.len, typename(OBJ_BLOB),
 +                                    sha1);
 +      else
 +              ret = hash_sha1_file(sbuf.buf, sbuf.len, typename(OBJ_BLOB),
 +                                   sha1);
 +      strbuf_release(&sbuf);
 +      return ret;
 +}
 +
  static int index_pipe(unsigned char *sha1, int fd, enum object_type type,
                      const char *path, unsigned flags)
  {
@@@ -3180,22 -3142,15 +3181,22 @@@ int index_fd(unsigned char *sha1, int f
             enum object_type type, const char *path, unsigned flags)
  {
        int ret;
 -      size_t size = xsize_t(st->st_size);
  
 -      if (!S_ISREG(st->st_mode))
 +      /*
 +       * Call xsize_t() only when needed to avoid potentially unnecessary
 +       * die() for large files.
 +       */
 +      if (type == OBJ_BLOB && path && would_convert_to_git_filter_fd(path))
 +              ret = index_stream_convert_blob(sha1, fd, path, flags);
 +      else if (!S_ISREG(st->st_mode))
                ret = index_pipe(sha1, fd, type, path, flags);
 -      else if (size <= big_file_threshold || type != OBJ_BLOB ||
 -               (path && would_convert_to_git(path, NULL, 0, 0)))
 -              ret = index_core(sha1, fd, size, type, path, flags);
 +      else if (st->st_size <= big_file_threshold || type != OBJ_BLOB ||
 +               (path && would_convert_to_git(path)))
 +              ret = index_core(sha1, fd, xsize_t(st->st_size), type, path,
 +                               flags);
        else
 -              ret = index_stream(sha1, fd, size, type, path, flags);
 +              ret = index_stream(sha1, fd, xsize_t(st->st_size), type, path,
 +                                 flags);
        close(fd);
        return ret;
  }