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
···
func (d *Handle) UploadPack(w http.ResponseWriter, r *http.Request) {
did := chi.URLParam(r, "did")
name := chi.URLParam(r, "name")
-
repo, _ := securejoin.SecureJoin(d.c.Repo.ScanPath, filepath.Join(did, name))
-
-
w.Header().Set("content-type", "application/x-git-upload-pack-result")
-
w.Header().Set("Connection", "Keep-Alive")
-
w.Header().Set("Transfer-Encoding", "chunked")
-
w.WriteHeader(http.StatusOK)
-
-
cmd := service.ServiceCommand{
-
Dir: repo,
-
Stdout: w,
+
repo, err := securejoin.SecureJoin(d.c.Repo.ScanPath, filepath.Join(did, name))
+
if err != nil {
+
writeError(w, err.Error(), 500)
+
d.l.Error("git: failed to secure join repo path", "handler", "UploadPack", "error", err)
+
return
}
-
var reader io.ReadCloser
-
reader = r.Body
-
+
var bodyReader io.ReadCloser = r.Body
if r.Header.Get("Content-Encoding") == "gzip" {
-
reader, err := gzip.NewReader(r.Body)
+
gzipReader, err := gzip.NewReader(r.Body)
if err != nil {
writeError(w, err.Error(), 500)
d.l.Error("git: failed to create gzip reader", "handler", "UploadPack", "error", err)
return
}
-
defer reader.Close()
+
defer gzipReader.Close()
+
bodyReader = gzipReader
+
}
+
+
w.Header().Set("Content-Type", "application/x-git-upload-pack-result")
+
w.Header().Set("Connection", "Keep-Alive")
+
+
d.l.Info("git: executing git-upload-pack", "handler", "UploadPack", "repo", repo)
+
+
cmd := service.ServiceCommand{
+
Dir: repo,
+
Stdout: w,
+
Stdin: bodyReader,
}
-
cmd.Stdin = reader
+
w.WriteHeader(http.StatusOK)
+
if err := cmd.UploadPack(); err != nil {
-
writeError(w, err.Error(), 500)
d.l.Error("git: failed to execute git-upload-pack", "handler", "UploadPack", "error", err)
return
}
+31 -24
knotserver/git/service/service.go
···
"net/http"
"os/exec"
"strings"
+
"sync"
"syscall"
)
···
}
func (c *ServiceCommand) UploadPack() error {
-
cmd := exec.Command("git", []string{
-
"-c", "uploadpack.allowFilter=true",
-
"upload-pack",
-
"--stateless-rpc",
-
".",
-
}...)
+
var stderr bytes.Buffer
+
+
cmd := exec.Command("git", "-c", "uploadpack.allowFilter=true",
+
"upload-pack", "--stateless-rpc", ".")
cmd.Dir = c.Dir
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
-
stdoutPipe, _ := cmd.StdoutPipe()
-
cmd.Stderr = cmd.Stdout
-
defer stdoutPipe.Close()
+
stdoutPipe, err := cmd.StdoutPipe()
+
if err != nil {
+
return fmt.Errorf("failed to create stdout pipe: %w", err)
+
}
+
+
cmd.Stderr = &stderr
stdinPipe, err := cmd.StdinPipe()
if err != nil {
-
return err
+
return fmt.Errorf("failed to create stdin pipe: %w", err)
}
-
defer stdinPipe.Close()
if err := cmd.Start(); err != nil {
-
log.Printf("git: failed to start git-upload-pack: %s", err)
-
return err
+
return fmt.Errorf("failed to start git-upload-pack: %w", err)
}
-
if _, err := io.Copy(stdinPipe, c.Stdin); err != nil {
-
log.Printf("git: failed to copy stdin: %s", err)
-
return err
-
}
-
stdinPipe.Close()
+
var wg sync.WaitGroup
+
+
wg.Add(1)
+
go func() {
+
defer wg.Done()
+
defer stdinPipe.Close()
+
io.Copy(stdinPipe, c.Stdin)
+
}()
+
+
wg.Add(1)
+
go func() {
+
defer wg.Done()
+
io.Copy(newWriteFlusher(c.Stdout), stdoutPipe)
+
stdoutPipe.Close()
+
}()
+
+
wg.Wait()
-
if _, err := io.Copy(newWriteFlusher(c.Stdout), stdoutPipe); err != nil {
-
log.Printf("git: failed to copy stdout: %s", err)
-
return err
-
}
if err := cmd.Wait(); err != nil {
-
log.Printf("git: failed to wait for git-upload-pack: %s", err)
-
return err
+
return fmt.Errorf("git-upload-pack failed: %w, stderr: %s", err, stderr.String())
}
return nil