> From: tomgra...@gmail.com

[Heavily cut -- see OP for the background.]

Gack!  Yuck!  Crufty code!  Someone should be shot!

Ugh, back to reality.  So I look at the trace just before the error:

> Below are the last few lines of the strace before it falls over:
> close(0)                                = 0
>  open(".git/objects/info/alternates", O_RDONLY|O_NOATIME) = -1 ENOENT (No 
> such file or directory)
>  access(".git/objects/99/564f15c5a06c44e372d03489c59a32ebcf9ec9", F_OK) = 0
>  access(".git/hooks/prepare-commit-msg", X_OK) = -1 ENOENT (No such file or 
> directory)
>  access(".git/hooks/commit-msg", X_OK)   = -1 ENOENT (No such file or 
> directory)
>  open(".git/COMMIT_EDITMSG", O_RDONLY)   = 0
>  read(0, "msg\n", 8192)                  = 4
>  read(0, "", 12308)                      = 0
>  close(0)                                = 0
>  open(".git/objects/99/564f15c5a06c44e372d03489c59a32ebcf9ec9", 
>  fstat(0, {st_mode=S_IFREG|0444, st_size=52, ...}) = 0
>  mmap(NULL, 52, PROT_READ, MAP_PRIVATE, 0, 0) = 0x7f26e51ad000
>  close(0)                                = 0
>  munmap(0x7f26e51ad000, 52)              = 0
>  access(".git/objects/f8/8d4f7df95a7c0b3842bf98533e01472efab496", F_OK) = 
> -1 ENOENT (No such file or directory)
>  open(".git/objects/f8/tmp_obj_pW5OIL", O_RDWR|O_CREAT|O_EXCL, 0444) = -1 
> ENOENT (No such file or directory)
>  mkdir(".git/objects/f8", 0777)          = 0
>  open(".git/objects/f8/tmp_obj_pkqRHL", O_RDWR|O_CREAT|O_EXCL, 0444) = 0
>  write(2, "error: unable to create temporar"..., 66error: unable to create 
> temporary file: No such file or directory
>  ) = 66

At this point, Git has decided that there is an error.  What
immediately preceeds this?  What I see is:

>  access(".git/objects/f8/8d4f7df95a7c0b3842bf98533e01472efab496", F_OK) = 
> -1 ENOENT (No such file or directory)
>  open(".git/objects/f8/tmp_obj_pW5OIL", O_RDWR|O_CREAT|O_EXCL, 0444) = -1 
> ENOENT (No such file or directory)
>  mkdir(".git/objects/f8", 0777)          = 0
>  open(".git/objects/f8/tmp_obj_pkqRHL", O_RDWR|O_CREAT|O_EXCL, 0444) = 0

(This is the sequence of kernel calls that the process executed.  Each
call is presented more or less as it could be written in a C program,
so you can read the manual pages and know what is happening.)

Meaning, Git is looking to see if a certain file exists and it can
read it.  The file doesn't exist, so Git tries to create it.  That
doesn't work because the containing directory doesn't exist, so Git
tries to create the directory.  mkdir works, so then Git tries to
create the file.  That works.

At this point, let's look again:  open() returns 0, which is a success
return, but it means that the file was opened on the fd which is
normally standard-input.  That *can* happen, but it's unusual -- fd 0
(standard-input) has to not be open beforehand.  If the program is
written correctly, that shouldn't cause a problem.

Hmmm...  I look at the examples again.  I see that the failure is seen
when and only when the open() returns 0.  This strongly suggests that
there is a bug in the code, in that the code assumes that open()
cannot return 0, and some variable or function is returning "the fd
that was opened, or 0 to indicate an error".  (The first belief turns
out to be correct, but the second believ turns out to be incorrect.)

I search the Git source for the text 'unable to create temporary
file'.  There is only one instance, in write_loose_object() in

static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
                              const void *buf, unsigned long len, time_t mtime)
        int fd, ret;
        unsigned char compressed[4096];
        git_zstream stream;
        git_SHA_CTX c;
        unsigned char parano_sha1[20];
        char *filename;
        static char tmp_file[PATH_MAX];

        filename = sha1_file_name(sha1);
        fd = create_tmpfile(tmp_file, sizeof(tmp_file), filename);
        if (fd < 0) {
                if (errno == EACCES)
                        return error("insufficient permission for adding an 
object to repository database %s", get_object_directory());
                        return error("unable to create temporary file: %s", 

Error 1:  There is no comment at the head of the function describing
its purpose, inputs, and outputs.

Clearly, the message will be printed if create_tmpfile returns a value
less than 0 and errno is not EACCES.

Looking at create_tmpfile():

 * This creates a temporary file in the same directory as the final
 * 'filename'
 * We want to avoid cross-directory filename renames, because those
 * can have problems on various filesystems (FAT, NFS, Coda).
static int create_tmpfile(char *buffer, size_t bufsiz, const char *filename)
        int fd, dirlen = directory_size(filename);

        if (dirlen + 20 > bufsiz) {
                errno = ENAMETOOLONG;
                return -1;
        memcpy(buffer, filename, dirlen);
        strcpy(buffer + dirlen, "tmp_obj_XXXXXX");
        fd = git_mkstemp_mode(buffer, 0444);
        if (fd < 0 && dirlen && errno == ENOENT) {
                /* Make sure the directory exists */
                memcpy(buffer, filename, dirlen);
                buffer[dirlen-1] = 0;
                if (mkdir(buffer, 0777) || adjust_shared_perm(buffer))
                        return -1;

                /* Try again */
                strcpy(buffer + dirlen - 1, "/tmp_obj_XXXXXX");
                fd = git_mkstemp_mode(buffer, 0444);
        return fd;

Error 2:  The inputs and the outputs of the function are not described
in the comment at the head.

Comparing the kernel calls that were seen (open() returning -1,
mkdir(), then open()) suggests that the first git_mkstemp_mode()
returned less than 0 (presumably an error return), then mkdir()
returned 0, then git_mkstemp_mode returned less than 0.

git_mkstemp_mode() is in wrapper.c and just calls git_mkstemps_mode:

int git_mkstemp_mode(char *pattern, int mode)
        /* mkstemp is just mkstemps with no suffix */
        return git_mkstemps_mode(pattern, 0, mode);

git_mkstemps_mode() is also in wrapper.c:

int git_mkstemps_mode(char *pattern, int suffix_len, int mode)
        static const char letters[] =
        static const int num_letters = 62;
        uint64_t value;
        struct timeval tv;
        char *template;
        size_t len;
        int fd, count;

        len = strlen(pattern);

        if (len < 6 + suffix_len) {
                errno = EINVAL;
                return -1;

        if (strncmp(&pattern[len - 6 - suffix_len], "XXXXXX", 6)) {
                errno = EINVAL;
                return -1;

         * Replace pattern's XXXXXX characters with randomness.
         * Try TMP_MAX different filenames.
        gettimeofday(&tv, NULL);
        value = ((size_t)(tv.tv_usec << 16)) ^ tv.tv_sec ^ getpid();
        template = &pattern[len - 6 - suffix_len];
        for (count = 0; count < TMP_MAX; ++count) {
                uint64_t v = value;
                /* Fill in the random bits. */
                template[0] = letters[v % num_letters]; v /= num_letters;
                template[1] = letters[v % num_letters]; v /= num_letters;
                template[2] = letters[v % num_letters]; v /= num_letters;
                template[3] = letters[v % num_letters]; v /= num_letters;
                template[4] = letters[v % num_letters]; v /= num_letters;
                template[5] = letters[v % num_letters]; v /= num_letters;

                fd = open(pattern, O_CREAT | O_EXCL | O_RDWR, mode);
                if (fd > 0)
                        return fd;
                 * Fatal error (EPERM, ENOSPC etc).
                 * It doesn't make sense to loop.
                if (errno != EEXIST)
                 * This is a random value.  It is only necessary that
                 * the next TMP_MAX values generated by adding 7777 to
                 * VALUE are different with (module 2^32).
                value += 7777;
        /* We return the null string if we can't find a unique file name.  */
        pattern[0] = '\0';
        return -1;

Error 3:  There is no comment at the head of the function describing
its purpose, inputs, and outputs.

This is a little more complex, but it appears that the function
constructs a series of semi-random temporary file names and attempts
to open them until an open succeeds.  Looking at the trace output, we
see that the open() was called only once during the last invocation of
git_mkstemps_mode(), with the value ".git/objects/f8/tmp_obj_pkqRHL".

We look at how the code will flow:
open() returns 0, so fd is set to 0.
if (fd > 0) is clearly intended to be the test "Did open() succeed?"
but it *fails* because fd is 0.  ***The coder assumed that open() could
not return 0.***
if (errno != EEXIST) *succeeds* (because errno happens not to be
EEXIST; it's probably still ENOENT), the break is taken, and
git_mkstemps_mode() returns -1.
git_mkstemp_mode() returns -1.
create_tmpfile() returns -1.
write_loose_object() declares the error.

So we see that:

1) The Git code is poorly documented, and there is no coding control
to ensure that the code is internally documented.

2) The test "if (fd > 0) return fd;" is incorrect.  It should be "(fd
>= 0)".

3) The error is triggered if fd 0 (standard-input) is closed when Git
starts and Git needs to put a file into the object store.  In your
situation, that is determined by the particular Git operation  and the
details of how Git is invoked by Apache.

>From this, we can construct a simple test case that demonstrates the

$ git --version
git version
$ git status
# On branch master
nothing to commit (working directory clean)
$ echo This is a test >ffff
$ git status
# On branch master
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#       ffff
nothing added to commit but untracked files present (use "git add" to track)
$ git add ffff
$ # The notation "0<&-" means "close standard input (fd 0) in the process that
$ # executes this command.  It may be specific to bash.
$ git commit -m xxxx 0<&-
error: unable to create temporary sha1 filename : No such file or directory

error: Error building trees
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#       new file:   ffff
$ git commit -m xxxx
[master 54c146c] xxxx
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 ffff
$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 1 commit.
nothing to commit (working directory clean)


You received this message because you are subscribed to the Google Groups "Git 
for human beings" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to git-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Reply via email to