Skip to content

Commit 096a32a

Browse files
author
Aaron Schlesinger
committed
fix(pkg/sshd/server.go): wrap the git receive logic in an RLock function
1 parent e7fc9c7 commit 096a32a

1 file changed

Lines changed: 30 additions & 18 deletions

File tree

pkg/sshd/server.go

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package sshd
99

1010
import (
11+
"errors"
1112
"fmt"
1213
"io"
1314
"net"
@@ -179,6 +180,12 @@ func sendExitStatus(status uint32, channel ssh.Channel) error {
179180
return err
180181
}
181182

183+
func (s *server) withCleanerRLock(f func() error) error {
184+
s.cleanerRef.RLock()
185+
defer s.cleanerRef.RUnlock()
186+
return f()
187+
}
188+
182189
// answer handles answering requests and channel requests
183190
//
184191
// Currently, an exec must be either "ping", "git-receive-pack" or
@@ -232,40 +239,45 @@ func (s *server) answer(channel ssh.Channel, requests <-chan *ssh.Request, sshCo
232239
}
233240

234241
repoName := parts[1]
235-
s.cleanerRef.RLock()
236-
if err := s.pushLock.Lock(repoName, time.Duration(0)); err != nil {
242+
errConcurrentPush := errors.New("concurrent push")
243+
err := s.withCleanerRLock(func() error {
244+
if err := s.pushLock.Lock(repoName, time.Duration(0)); err != nil {
245+
return errConcurrentPush
246+
}
247+
248+
req.Reply(true, nil) // We processed. Yay.
249+
250+
cxt.Put("channel", channel)
251+
cxt.Put("request", req)
252+
cxt.Put("operation", parts[0])
253+
cxt.Put("repository", parts[1])
254+
sshGitReceive := cxt.Get("route.sshd.sshGitReceive", "sshGitReceive").(string)
255+
err := router.HandleRequest(sshGitReceive, cxt, true)
256+
if err := s.pushLock.Unlock(repoName, time.Duration(0)); err != nil {
257+
log.Errf(s.c, "unable to unlock repository lock for %s (%s)", repoName, err)
258+
// TODO: this is an important error case that needs to be covered
259+
// Probably the best solution is to change the lock into a lease so that even on unlock failures, RepositoryLock will eventually yield
260+
}
261+
return err
262+
})
263+
264+
if err == errConcurrentPush {
237265
log.Errf(s.c, multiplePush)
238266
// The error must be in git format
239267
if err := gitPktLine(channel, fmt.Sprintf("ERR %v\n", multiplePush)); err != nil {
240268
log.Errf(s.c, "Failed to write to channel: %s", err)
241269
}
242270
sendExitStatus(1, channel)
243271
req.Reply(false, nil)
244-
s.cleanerRef.RUnlock()
245272
return nil
246273
}
247274

248-
req.Reply(true, nil) // We processed. Yay.
249-
250-
cxt.Put("channel", channel)
251-
cxt.Put("request", req)
252-
cxt.Put("operation", parts[0])
253-
cxt.Put("repository", parts[1])
254-
sshGitReceive := cxt.Get("route.sshd.sshGitReceive", "sshGitReceive").(string)
255-
err := router.HandleRequest(sshGitReceive, cxt, true)
256-
if err := s.pushLock.Unlock(repoName, time.Duration(0)); err != nil {
257-
log.Errf(s.c, "unable to unlock repository lock for %s (%s)", repoName, err)
258-
// TODO: this is an important error case that needs to be covered
259-
// Probably the best solution is to change the lock into a lease so that even on unlock failures, RepositoryLock will eventually yield
260-
}
261-
s.cleanerRef.RUnlock()
262275
var xs uint32
263276
if err != nil {
264277
log.Errf(s.c, "Failed git receive: %v", err)
265278
xs = 1
266279
}
267280
sendExitStatus(xs, channel)
268-
269281
return nil
270282
default:
271283
log.Warnf(s.c, "Illegal command is '%s'\n", clean)

0 commit comments

Comments
 (0)