|
8 | 8 | package sshd |
9 | 9 |
|
10 | 10 | import ( |
| 11 | + "errors" |
11 | 12 | "fmt" |
12 | 13 | "io" |
13 | 14 | "net" |
@@ -179,6 +180,12 @@ func sendExitStatus(status uint32, channel ssh.Channel) error { |
179 | 180 | return err |
180 | 181 | } |
181 | 182 |
|
| 183 | +func (s *server) withCleanerRLock(f func() error) error { |
| 184 | + s.cleanerRef.RLock() |
| 185 | + defer s.cleanerRef.RUnlock() |
| 186 | + return f() |
| 187 | +} |
| 188 | + |
182 | 189 | // answer handles answering requests and channel requests |
183 | 190 | // |
184 | 191 | // 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 |
232 | 239 | } |
233 | 240 |
|
234 | 241 | 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 { |
237 | 265 | log.Errf(s.c, multiplePush) |
238 | 266 | // The error must be in git format |
239 | 267 | if err := gitPktLine(channel, fmt.Sprintf("ERR %v\n", multiplePush)); err != nil { |
240 | 268 | log.Errf(s.c, "Failed to write to channel: %s", err) |
241 | 269 | } |
242 | 270 | sendExitStatus(1, channel) |
243 | 271 | req.Reply(false, nil) |
244 | | - s.cleanerRef.RUnlock() |
245 | 272 | return nil |
246 | 273 | } |
247 | 274 |
|
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() |
262 | 275 | var xs uint32 |
263 | 276 | if err != nil { |
264 | 277 | log.Errf(s.c, "Failed git receive: %v", err) |
265 | 278 | xs = 1 |
266 | 279 | } |
267 | 280 | sendExitStatus(xs, channel) |
268 | | - |
269 | 281 | return nil |
270 | 282 | default: |
271 | 283 | log.Warnf(s.c, "Illegal command is '%s'\n", clean) |
|
0 commit comments