Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(205)

Delta Between Two Patch Sets: ssh/session.go

Issue 14225043: code review 14225043: go.crypto/ssh: reimplement SSH connection protocol modu... (Closed)
Left Patch Set: diff -r 2cd6b3b93cdb https://code.google.com/p/go.crypto Created 10 years, 6 months ago
Right Patch Set: diff -r cd1eea1eb828 https://code.google.com/p/go.crypto Created 10 years, 5 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
Right: Side by side diff | Download
« no previous file with change/comment | « ssh/server_terminal.go ('k') | ssh/session_test.go » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
(no file at all)
1 // Copyright 2011 The Go Authors. All rights reserved. 1 // Copyright 2011 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style 2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file. 3 // license that can be found in the LICENSE file.
4 4
5 package ssh 5 package ssh
6 6
7 // Session implements an interactive session described in 7 // Session implements an interactive session described in
8 // "RFC 4254, section 6". 8 // "RFC 4254, section 6".
9 9
10 import ( 10 import (
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after
122 // output and error. 122 // output and error.
123 // 123 //
124 // If either is nil, Run connects the corresponding file 124 // If either is nil, Run connects the corresponding file
125 // descriptor to an instance of ioutil.Discard. There is a 125 // descriptor to an instance of ioutil.Discard. There is a
126 // fixed amount of buffering that is shared for the two streams. 126 // fixed amount of buffering that is shared for the two streams.
127 // If either blocks it may eventually cause the remote 127 // If either blocks it may eventually cause the remote
128 // command to block. 128 // command to block.
129 Stdout io.Writer 129 Stdout io.Writer
130 Stderr io.Writer 130 Stderr io.Writer
131 131
132 » *clientChan // the channel backing this session 132 » ch *channel // the channel backing this session
133 133
134 started bool // true once Start, Run or Shell is invoked. 134 started bool // true once Start, Run or Shell is invoked.
135 copyFuncs []func() error 135 copyFuncs []func() error
136 errors chan error // one send per copyFunc 136 errors chan error // one send per copyFunc
137 137
138 // true if pipe method is active 138 // true if pipe method is active
139 stdinpipe, stdoutpipe, stderrpipe bool 139 stdinpipe, stdoutpipe, stderrpipe bool
140 } 140 }
141 141
142 func (s *Session) Close() error {
143 return s.ch.Close()
144 }
145
142 // RFC 4254 Section 6.4. 146 // RFC 4254 Section 6.4.
143 type setenvRequest struct { 147 type setenvRequest struct {
144 » PeersId uint32 148 » Name string
145 » Request string 149 » Value string
146 » WantReply bool
147 » Name string
148 » Value string
149 }
150
151 // RFC 4254 Section 6.5.
152 type subsystemRequestMsg struct {
153 » PeersId uint32
154 » Request string
155 » WantReply bool
156 » Subsystem string
157 } 150 }
158 151
159 // Setenv sets an environment variable that will be applied to any 152 // Setenv sets an environment variable that will be applied to any
160 // command executed by Shell or Run. 153 // command executed by Shell or Run.
161 func (s *Session) Setenv(name, value string) error { 154 func (s *Session) Setenv(name, value string) error {
162 » req := setenvRequest{ 155 » msg := setenvRequest{
163 » » PeersId: s.remoteId, 156 » » Name: name,
164 » » Request: "env", 157 » » Value: value,
165 » » WantReply: true, 158 » }
166 » » Name: name, 159 » ok, err := s.ch.SendRequest("env", true, marshal(0, msg))
167 » » Value: value, 160 » if err == nil && !ok {
168 » } 161 » » err = errors.New("ssh: setenv failed")
169 » if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil { 162 » }
170 » » return err 163 » return err
171 » }
172 » return s.waitForResponse()
173 } 164 }
174 165
175 // RFC 4254 Section 6.2. 166 // RFC 4254 Section 6.2.
176 type ptyRequestMsg struct { 167 type ptyRequestMsg struct {
177 » PeersId uint32 168 » Term string
178 » Request string 169 » Columns uint32
179 » WantReply bool 170 » Rows uint32
180 » Term string 171 » Width uint32
181 » Columns uint32 172 » Height uint32
182 » Rows uint32 173 » Modelist string
183 » Width uint32
184 » Height uint32
185 » Modelist string
186 } 174 }
187 175
188 // RequestPty requests the association of a pty with the session on the remote h ost. 176 // RequestPty requests the association of a pty with the session on the remote h ost.
189 func (s *Session) RequestPty(term string, h, w int, termmodes TerminalModes) err or { 177 func (s *Session) RequestPty(term string, h, w int, termmodes TerminalModes) err or {
190 var tm []byte 178 var tm []byte
191 for k, v := range termmodes { 179 for k, v := range termmodes {
192 tm = append(tm, k) 180 tm = append(tm, k)
193 tm = appendU32(tm, v) 181 tm = appendU32(tm, v)
194 } 182 }
195 tm = append(tm, tty_OP_END) 183 tm = append(tm, tty_OP_END)
196 req := ptyRequestMsg{ 184 req := ptyRequestMsg{
197 » » PeersId: s.remoteId, 185 » » Term: term,
198 » » Request: "pty-req", 186 » » Columns: uint32(w),
199 » » WantReply: true, 187 » » Rows: uint32(h),
200 » » Term: term, 188 » » Width: uint32(w * 8),
201 » » Columns: uint32(w), 189 » » Height: uint32(h * 8),
202 » » Rows: uint32(h), 190 » » Modelist: string(tm),
203 » » Width: uint32(w * 8), 191 » }
204 » » Height: uint32(h * 8), 192 » ok, err := s.ch.SendRequest("pty-req", true, marshal(0, req))
205 » » Modelist: string(tm), 193 » if err == nil && !ok {
206 » } 194 » » err = errors.New("ssh: pty-req failed")
207 » if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil { 195 » }
208 » » return err 196 » return err
209 » } 197 }
210 » return s.waitForResponse() 198
199 // RFC 4254 Section 6.5.
200 type subsystemRequestMsg struct {
201 » Subsystem string
211 } 202 }
212 203
213 // RequestSubsystem requests the association of a subsystem with the session on the remote host. 204 // RequestSubsystem requests the association of a subsystem with the session on the remote host.
214 // A subsystem is a predefined command that runs in the background when the ssh session is initiated 205 // A subsystem is a predefined command that runs in the background when the ssh session is initiated
215 func (s *Session) RequestSubsystem(subsystem string) error { 206 func (s *Session) RequestSubsystem(subsystem string) error {
216 » req := subsystemRequestMsg{ 207 » msg := subsystemRequestMsg{
217 » » PeersId: s.remoteId,
218 » » Request: "subsystem",
219 » » WantReply: true,
220 Subsystem: subsystem, 208 Subsystem: subsystem,
221 } 209 }
222 » if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil { 210 » ok, err := s.ch.SendRequest("subsystem", true, marshal(0, msg))
223 » » return err 211 » if err == nil && !ok {
224 » } 212 » » err = errors.New("ssh: subsystem request failed")
225 » return s.waitForResponse() 213 » }
214 » return err
226 } 215 }
227 216
228 // RFC 4254 Section 6.9. 217 // RFC 4254 Section 6.9.
229 type signalMsg struct { 218 type signalMsg struct {
230 » PeersId uint32 219 » Signal string
231 » Request string
232 » WantReply bool
233 » Signal string
234 } 220 }
235 221
236 // Signal sends the given signal to the remote process. 222 // Signal sends the given signal to the remote process.
237 // sig is one of the SIG* constants. 223 // sig is one of the SIG* constants.
238 func (s *Session) Signal(sig Signal) error { 224 func (s *Session) Signal(sig Signal) error {
239 » req := signalMsg{ 225 » msg := signalMsg{
240 » » PeersId: s.remoteId, 226 » » Signal: string(sig),
241 » » Request: "signal", 227 » }
242 » » WantReply: false, 228
243 » » Signal: string(sig), 229 » _, err := s.ch.SendRequest("signal", false, marshal(0, msg))
244 » } 230 » return err
245 » return s.writePacket(marshal(msgChannelRequest, req))
246 } 231 }
247 232
248 // RFC 4254 Section 6.5. 233 // RFC 4254 Section 6.5.
249 type execMsg struct { 234 type execMsg struct {
250 » PeersId uint32 235 » Command string
251 » Request string
252 » WantReply bool
253 » Command string
254 } 236 }
255 237
256 // Start runs cmd on the remote host. Typically, the remote 238 // Start runs cmd on the remote host. Typically, the remote
257 // server passes cmd to the shell for interpretation. 239 // server passes cmd to the shell for interpretation.
258 // A Session only accepts one call to Run, Start or Shell. 240 // A Session only accepts one call to Run, Start or Shell.
259 func (s *Session) Start(cmd string) error { 241 func (s *Session) Start(cmd string) error {
260 if s.started { 242 if s.started {
261 return errors.New("ssh: session already started") 243 return errors.New("ssh: session already started")
262 } 244 }
263 req := execMsg{ 245 req := execMsg{
264 » » PeersId: s.remoteId, 246 » » Command: cmd,
265 » » Request: "exec", 247 » }
266 » » WantReply: true, 248
267 » » Command: cmd, 249 » ok, err := s.ch.SendRequest("exec", true, marshal(0, req))
268 » } 250 » if err == nil && !ok {
269 » if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil { 251 » » err = fmt.Errorf("ssh: command %v failed", cmd)
252 » }
253 » if err != nil {
270 return err 254 return err
271 }
272 if err := s.waitForResponse(); err != nil {
273 return fmt.Errorf("ssh: could not execute command %s: %v", cmd, err)
274 } 255 }
275 return s.start() 256 return s.start()
276 } 257 }
277 258
278 // Run runs cmd on the remote host. Typically, the remote 259 // Run runs cmd on the remote host. Typically, the remote
279 // server passes cmd to the shell for interpretation. 260 // server passes cmd to the shell for interpretation.
280 // A Session only accepts one call to Run, Start, Shell, Output, 261 // A Session only accepts one call to Run, Start, Shell, Output,
281 // or CombinedOutput. 262 // or CombinedOutput.
282 // 263 //
283 // The returned error is nil if the command runs, has no problems 264 // The returned error is nil if the command runs, has no problems
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
332 err := s.Run(cmd) 313 err := s.Run(cmd)
333 return b.b.Bytes(), err 314 return b.b.Bytes(), err
334 } 315 }
335 316
336 // Shell starts a login shell on the remote host. A Session only 317 // Shell starts a login shell on the remote host. A Session only
337 // accepts one call to Run, Start, Shell, Output, or CombinedOutput. 318 // accepts one call to Run, Start, Shell, Output, or CombinedOutput.
338 func (s *Session) Shell() error { 319 func (s *Session) Shell() error {
339 if s.started { 320 if s.started {
340 return errors.New("ssh: session already started") 321 return errors.New("ssh: session already started")
341 } 322 }
342 » req := channelRequestMsg{ 323
343 » » PeersId: s.remoteId, 324 » ok, err := s.ch.SendRequest("shell", true, nil)
344 » » Request: "shell", 325 » if err == nil && !ok {
345 » » WantReply: true, 326 » » return fmt.Errorf("ssh: cound not execute")
346 » } 327 » }
347 » if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil { 328 » if err != nil {
348 return err 329 return err
349 } 330 }
350 if err := s.waitForResponse(); err != nil {
351 return fmt.Errorf("ssh: could not execute shell: %v", err)
352 }
353 return s.start() 331 return s.start()
354 }
355
356 func (s *Session) waitForResponse() error {
357 msg := <-s.msg
358 switch msg.(type) {
359 case *channelRequestSuccessMsg:
360 return nil
361 case *channelRequestFailureMsg:
362 return errors.New("ssh: request failed")
363 }
364 return fmt.Errorf("ssh: unknown packet %T received: %v", msg, msg)
365 } 332 }
366 333
367 func (s *Session) start() error { 334 func (s *Session) start() error {
368 s.started = true 335 s.started = true
369 336
370 type F func(*Session) 337 type F func(*Session)
371 for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Sessi on).stderr} { 338 for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Sessi on).stderr} {
372 setupFd(s) 339 setupFd(s)
373 } 340 }
374 341
(...skipping 30 matching lines...) Expand all
405 if waitErr != nil { 372 if waitErr != nil {
406 return waitErr 373 return waitErr
407 } 374 }
408 return copyError 375 return copyError
409 } 376 }
410 377
411 func (s *Session) wait() error { 378 func (s *Session) wait() error {
412 wm := Waitmsg{status: -1} 379 wm := Waitmsg{status: -1}
413 380
414 // Wait for msg channel to be closed before returning. 381 // Wait for msg channel to be closed before returning.
415 » for msg := range s.msg { 382 » for msg := range s.ch.incomingRequests {
416 » » switch msg := msg.(type) { 383 » » switch msg.Request {
417 » » case *channelRequestMsg: 384 » » case "exit-status":
418 » » » switch msg.Request { 385 » » » d := msg.Payload
419 » » » case "exit-status": 386 » » » wm.status = int(d[0])<<24 | int(d[1])<<16 | int(d[2])<<8 | int(d[3])
420 » » » » d := msg.RequestSpecificData 387 » » case "exit-signal":
421 » » » » wm.status = int(d[0])<<24 | int(d[1])<<16 | int( d[2])<<8 | int(d[3]) 388 » » » signal, rest, ok := parseString(msg.Payload)
422 » » » case "exit-signal": 389 » » » if !ok {
423 » » » » signal, rest, ok := parseString(msg.RequestSpeci ficData) 390 » » » » return fmt.Errorf("wait: could not parse request data: %v", msg.Payload)
424 » » » » if !ok {
425 » » » » » return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
426 » » » » }
427 » » » » wm.signal = safeString(string(signal))
428
429 » » » » // skip coreDumped bool
430 » » » » if len(rest) == 0 {
431 » » » » » return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
432 » » » » }
433 » » » » rest = rest[1:]
434
435 » » » » errmsg, rest, ok := parseString(rest)
436 » » » » if !ok {
437 » » » » » return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
438 » » » » }
439 » » » » wm.msg = safeString(string(errmsg))
440
441 » » » » lang, _, ok := parseString(rest)
442 » » » » if !ok {
443 » » » » » return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
444 » » » » }
445 » » » » wm.lang = safeString(string(lang))
446 » » » default:
447 » » » » // This handles keepalives and matches
448 » » » » // OpenSSH's behaviour.
449 » » » » if msg.WantReply {
450 » » » » » s.writePacket(marshal(msgChannelFailure, channelRequestFailureMsg{
451 » » » » » » PeersId: s.remoteId,
452 » » » » » }))
453 » » » » }
454 } 391 }
392 wm.signal = safeString(string(signal))
393
394 // skip coreDumped bool
395 if len(rest) == 0 {
396 return fmt.Errorf("wait: could not parse request data: %v", msg.Payload)
397 }
398 rest = rest[1:]
399
400 errmsg, rest, ok := parseString(rest)
401 if !ok {
402 return fmt.Errorf("wait: could not parse request data: %v", msg.Payload)
403 }
404 wm.msg = safeString(string(errmsg))
405
406 lang, _, ok := parseString(rest)
407 if !ok {
408 return fmt.Errorf("wait: could not parse request data: %v", msg.Payload)
409 }
410 wm.lang = safeString(string(lang))
455 default: 411 default:
456 » » » return fmt.Errorf("wait: unexpected packet %T received: %v", msg, msg) 412 » » » // This handles keepalives and matches
413 » » » // OpenSSH's behaviour.
414 » » » if msg.WantReply {
415 » » » » s.ch.AckRequest(false)
416 » » » }
457 } 417 }
458 } 418 }
459 if wm.status == 0 { 419 if wm.status == 0 {
460 return nil 420 return nil
461 } 421 }
462 if wm.status == -1 { 422 if wm.status == -1 {
463 // exit-status was never sent from server 423 // exit-status was never sent from server
464 if wm.signal == "" { 424 if wm.signal == "" {
465 return errors.New("wait: remote command exited without e xit status or exit signal") 425 return errors.New("wait: remote command exited without e xit status or exit signal")
466 } 426 }
467 wm.status = 128 427 wm.status = 128
468 if _, ok := signals[Signal(wm.signal)]; ok { 428 if _, ok := signals[Signal(wm.signal)]; ok {
469 wm.status += signals[Signal(wm.signal)] 429 wm.status += signals[Signal(wm.signal)]
470 } 430 }
471 } 431 }
472 return &ExitError{wm} 432 return &ExitError{wm}
473 } 433 }
474 434
475 func (s *Session) stdin() { 435 func (s *Session) stdin() {
476 if s.stdinpipe { 436 if s.stdinpipe {
477 return 437 return
478 } 438 }
479 if s.Stdin == nil { 439 if s.Stdin == nil {
480 s.Stdin = new(bytes.Buffer) 440 s.Stdin = new(bytes.Buffer)
481 } 441 }
482 s.copyFuncs = append(s.copyFuncs, func() error { 442 s.copyFuncs = append(s.copyFuncs, func() error {
483 » » _, err := io.Copy(s.clientChan.stdin, s.Stdin) 443 » » _, err := io.Copy(s.ch, s.Stdin)
484 » » if err1 := s.clientChan.stdin.Close(); err == nil && err1 != io. EOF { 444 » » if err1 := s.ch.CloseWrite(); err == nil && err1 != io.EOF {
485 err = err1 445 err = err1
486 } 446 }
487 return err 447 return err
488 }) 448 })
489 } 449 }
490 450
491 func (s *Session) stdout() { 451 func (s *Session) stdout() {
492 if s.stdoutpipe { 452 if s.stdoutpipe {
493 return 453 return
494 } 454 }
495 if s.Stdout == nil { 455 if s.Stdout == nil {
496 s.Stdout = ioutil.Discard 456 s.Stdout = ioutil.Discard
497 } 457 }
498 s.copyFuncs = append(s.copyFuncs, func() error { 458 s.copyFuncs = append(s.copyFuncs, func() error {
499 » » _, err := io.Copy(s.Stdout, s.clientChan.stdout) 459 » » _, err := io.Copy(s.Stdout, s.ch)
500 return err 460 return err
501 }) 461 })
502 } 462 }
503 463
504 func (s *Session) stderr() { 464 func (s *Session) stderr() {
505 if s.stderrpipe { 465 if s.stderrpipe {
506 return 466 return
507 } 467 }
508 if s.Stderr == nil { 468 if s.Stderr == nil {
509 s.Stderr = ioutil.Discard 469 s.Stderr = ioutil.Discard
510 } 470 }
511 s.copyFuncs = append(s.copyFuncs, func() error { 471 s.copyFuncs = append(s.copyFuncs, func() error {
512 » » _, err := io.Copy(s.Stderr, s.clientChan.stderr) 472 » » _, err := io.Copy(s.Stderr, s.ch.Extended(1))
513 return err 473 return err
514 }) 474 })
475 }
476
477 // sessionStdin reroutes Close to CloseWrite.
478 type sessionStdin struct {
479 io.Writer
480 ch *channel
481 }
482
483 func (s *sessionStdin) Close() error {
484 return s.ch.CloseWrite()
515 } 485 }
516 486
517 // StdinPipe returns a pipe that will be connected to the 487 // StdinPipe returns a pipe that will be connected to the
518 // remote command's standard input when the command starts. 488 // remote command's standard input when the command starts.
519 func (s *Session) StdinPipe() (io.WriteCloser, error) { 489 func (s *Session) StdinPipe() (io.WriteCloser, error) {
520 if s.Stdin != nil { 490 if s.Stdin != nil {
521 return nil, errors.New("ssh: Stdin already set") 491 return nil, errors.New("ssh: Stdin already set")
522 } 492 }
523 if s.started { 493 if s.started {
524 return nil, errors.New("ssh: StdinPipe after process started") 494 return nil, errors.New("ssh: StdinPipe after process started")
525 } 495 }
526 s.stdinpipe = true 496 s.stdinpipe = true
527 » return s.clientChan.stdin, nil 497 » return &sessionStdin{s.ch, s.ch}, nil
528 } 498 }
529 499
530 // StdoutPipe returns a pipe that will be connected to the 500 // StdoutPipe returns a pipe that will be connected to the
531 // remote command's standard output when the command starts. 501 // remote command's standard output when the command starts.
532 // There is a fixed amount of buffering that is shared between 502 // There is a fixed amount of buffering that is shared between
533 // stdout and stderr streams. If the StdoutPipe reader is 503 // stdout and stderr streams. If the StdoutPipe reader is
534 // not serviced fast enough it may eventually cause the 504 // not serviced fast enough it may eventually cause the
535 // remote command to block. 505 // remote command to block.
536 func (s *Session) StdoutPipe() (io.Reader, error) { 506 func (s *Session) StdoutPipe() (io.Reader, error) {
537 if s.Stdout != nil { 507 if s.Stdout != nil {
538 return nil, errors.New("ssh: Stdout already set") 508 return nil, errors.New("ssh: Stdout already set")
539 } 509 }
540 if s.started { 510 if s.started {
541 return nil, errors.New("ssh: StdoutPipe after process started") 511 return nil, errors.New("ssh: StdoutPipe after process started")
542 } 512 }
543 s.stdoutpipe = true 513 s.stdoutpipe = true
544 » return s.clientChan.stdout, nil 514 » return s.ch, nil
545 } 515 }
546 516
547 // StderrPipe returns a pipe that will be connected to the 517 // StderrPipe returns a pipe that will be connected to the
548 // remote command's standard error when the command starts. 518 // remote command's standard error when the command starts.
549 // There is a fixed amount of buffering that is shared between 519 // There is a fixed amount of buffering that is shared between
550 // stdout and stderr streams. If the StderrPipe reader is 520 // stdout and stderr streams. If the StderrPipe reader is
551 // not serviced fast enough it may eventually cause the 521 // not serviced fast enough it may eventually cause the
552 // remote command to block. 522 // remote command to block.
553 func (s *Session) StderrPipe() (io.Reader, error) { 523 func (s *Session) StderrPipe() (io.Reader, error) {
554 if s.Stderr != nil { 524 if s.Stderr != nil {
555 return nil, errors.New("ssh: Stderr already set") 525 return nil, errors.New("ssh: Stderr already set")
556 } 526 }
557 if s.started { 527 if s.started {
558 return nil, errors.New("ssh: StderrPipe after process started") 528 return nil, errors.New("ssh: StderrPipe after process started")
559 } 529 }
560 s.stderrpipe = true 530 s.stderrpipe = true
561 » return s.clientChan.stderr, nil 531 » return s.ch.Extended(1), nil
562 } 532 }
563 533
564 // NewSession returns a new interactive session on the remote host. 534 // NewSession returns a new interactive session on the remote host.
565 func (c *ClientConn) NewSession() (*Session, error) { 535 func (c *ClientConn) NewSession() (*Session, error) {
566 » ch := c.newChan(c.transport) 536 » ch, err := c.mux.OpenChannel("session", nil)
567 » if err := c.transport.writePacket(marshal(msgChannelOpen, channelOpenMsg { 537 » if err != nil {
568 » » ChanType: "session",
569 » » PeersId: ch.localId,
570 » » PeersWindow: 1 << 14,
571 » » MaxPacketSize: 1 << 15, // RFC 4253 6.1
572 » })); err != nil {
573 » » c.chanList.remove(ch.localId)
574 return nil, err 538 return nil, err
575 } 539 }
576 if err := ch.waitForChannelOpenResponse(); err != nil {
577 c.chanList.remove(ch.localId)
578 return nil, fmt.Errorf("ssh: unable to open session: %v", err)
579 }
580 return &Session{ 540 return &Session{
581 » » clientChan: ch, 541 » » ch: ch,
582 }, nil 542 }, nil
583 } 543 }
584 544
585 // An ExitError reports unsuccessful completion of a remote command. 545 // An ExitError reports unsuccessful completion of a remote command.
586 type ExitError struct { 546 type ExitError struct {
587 Waitmsg 547 Waitmsg
588 } 548 }
589 549
590 func (e *ExitError) Error() string { 550 func (e *ExitError) Error() string {
591 return e.Waitmsg.String() 551 return e.Waitmsg.String()
(...skipping 25 matching lines...) Expand all
617 } 577 }
618 578
619 // Lang returns the language tag. See RFC 3066 579 // Lang returns the language tag. See RFC 3066
620 func (w Waitmsg) Lang() string { 580 func (w Waitmsg) Lang() string {
621 return w.lang 581 return w.lang
622 } 582 }
623 583
624 func (w Waitmsg) String() string { 584 func (w Waitmsg) String() string {
625 return fmt.Sprintf("Process exited with: %v. Reason was: %v (%v)", w.sta tus, w.msg, w.signal) 585 return fmt.Sprintf("Process exited with: %v. Reason was: %v (%v)", w.sta tus, w.msg, w.signal)
626 } 586 }
LEFTRIGHT

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b