forked from tangled.org/core
this repo has no description

knotserver: git: fix upload-pack handling

Fix stdin piping to git-upload-pack. I think we were closing the pipe
too soon previously, resulting in the 'fatal: remote end hung up
unexpectedly' error. Probably went unnoticed for smaller repos.

Also fix a bunch of misc. bugs in with superfluous http header writes
and the gzip reader being shadowed.

anirudh.fi 5fc76ee3 90cab256

verified
Changed files
+53 -41
knotserver
git
service
+22 -17
knotserver/git.go
··· 34 34 func (d *Handle) UploadPack(w http.ResponseWriter, r *http.Request) { 35 35 did := chi.URLParam(r, "did") 36 36 name := chi.URLParam(r, "name") 37 - repo, _ := securejoin.SecureJoin(d.c.Repo.ScanPath, filepath.Join(did, name)) 38 - 39 - w.Header().Set("content-type", "application/x-git-upload-pack-result") 40 - w.Header().Set("Connection", "Keep-Alive") 41 - w.Header().Set("Transfer-Encoding", "chunked") 42 - w.WriteHeader(http.StatusOK) 43 - 44 - cmd := service.ServiceCommand{ 45 - Dir: repo, 46 - Stdout: w, 37 + repo, err := securejoin.SecureJoin(d.c.Repo.ScanPath, filepath.Join(did, name)) 38 + if err != nil { 39 + writeError(w, err.Error(), 500) 40 + d.l.Error("git: failed to secure join repo path", "handler", "UploadPack", "error", err) 41 + return 47 42 } 48 43 49 - var reader io.ReadCloser 50 - reader = r.Body 51 - 44 + var bodyReader io.ReadCloser = r.Body 52 45 if r.Header.Get("Content-Encoding") == "gzip" { 53 - reader, err := gzip.NewReader(r.Body) 46 + gzipReader, err := gzip.NewReader(r.Body) 54 47 if err != nil { 55 48 writeError(w, err.Error(), 500) 56 49 d.l.Error("git: failed to create gzip reader", "handler", "UploadPack", "error", err) 57 50 return 58 51 } 59 - defer reader.Close() 52 + defer gzipReader.Close() 53 + bodyReader = gzipReader 54 + } 55 + 56 + w.Header().Set("Content-Type", "application/x-git-upload-pack-result") 57 + w.Header().Set("Connection", "Keep-Alive") 58 + 59 + d.l.Info("git: executing git-upload-pack", "handler", "UploadPack", "repo", repo) 60 + 61 + cmd := service.ServiceCommand{ 62 + Dir: repo, 63 + Stdout: w, 64 + Stdin: bodyReader, 60 65 } 61 66 62 - cmd.Stdin = reader 67 + w.WriteHeader(http.StatusOK) 68 + 63 69 if err := cmd.UploadPack(); err != nil { 64 - writeError(w, err.Error(), 500) 65 70 d.l.Error("git: failed to execute git-upload-pack", "handler", "UploadPack", "error", err) 66 71 return 67 72 }
+31 -24
knotserver/git/service/service.go
··· 8 8 "net/http" 9 9 "os/exec" 10 10 "strings" 11 + "sync" 11 12 "syscall" 12 13 ) 13 14 ··· 68 69 } 69 70 70 71 func (c *ServiceCommand) UploadPack() error { 71 - cmd := exec.Command("git", []string{ 72 - "-c", "uploadpack.allowFilter=true", 73 - "upload-pack", 74 - "--stateless-rpc", 75 - ".", 76 - }...) 72 + var stderr bytes.Buffer 73 + 74 + cmd := exec.Command("git", "-c", "uploadpack.allowFilter=true", 75 + "upload-pack", "--stateless-rpc", ".") 77 76 cmd.Dir = c.Dir 78 77 cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} 79 78 80 - stdoutPipe, _ := cmd.StdoutPipe() 81 - cmd.Stderr = cmd.Stdout 82 - defer stdoutPipe.Close() 79 + stdoutPipe, err := cmd.StdoutPipe() 80 + if err != nil { 81 + return fmt.Errorf("failed to create stdout pipe: %w", err) 82 + } 83 + 84 + cmd.Stderr = &stderr 83 85 84 86 stdinPipe, err := cmd.StdinPipe() 85 87 if err != nil { 86 - return err 88 + return fmt.Errorf("failed to create stdin pipe: %w", err) 87 89 } 88 - defer stdinPipe.Close() 89 90 90 91 if err := cmd.Start(); err != nil { 91 - log.Printf("git: failed to start git-upload-pack: %s", err) 92 - return err 92 + return fmt.Errorf("failed to start git-upload-pack: %w", err) 93 93 } 94 94 95 - if _, err := io.Copy(stdinPipe, c.Stdin); err != nil { 96 - log.Printf("git: failed to copy stdin: %s", err) 97 - return err 98 - } 99 - stdinPipe.Close() 95 + var wg sync.WaitGroup 96 + 97 + wg.Add(1) 98 + go func() { 99 + defer wg.Done() 100 + defer stdinPipe.Close() 101 + io.Copy(stdinPipe, c.Stdin) 102 + }() 103 + 104 + wg.Add(1) 105 + go func() { 106 + defer wg.Done() 107 + io.Copy(newWriteFlusher(c.Stdout), stdoutPipe) 108 + stdoutPipe.Close() 109 + }() 110 + 111 + wg.Wait() 100 112 101 - if _, err := io.Copy(newWriteFlusher(c.Stdout), stdoutPipe); err != nil { 102 - log.Printf("git: failed to copy stdout: %s", err) 103 - return err 104 - } 105 113 if err := cmd.Wait(); err != nil { 106 - log.Printf("git: failed to wait for git-upload-pack: %s", err) 107 - return err 114 + return fmt.Errorf("git-upload-pack failed: %w, stderr: %s", err, stderr.String()) 108 115 } 109 116 110 117 return nil