doAttach spawns the screen client and starts the heartbeat. It exists separately only so we can defer the mutex unlock which is not possible in Attach since it blocks.
(ctx context.Context, conn net.Conn, height, width uint16, logger slog.Logger)
| 216 | // separately only so we can defer the mutex unlock which is not possible in |
| 217 | // Attach since it blocks. |
| 218 | func (rpty *screenReconnectingPTY) doAttach(ctx context.Context, conn net.Conn, height, width uint16, logger slog.Logger) (pty.PTYCmd, pty.Process, error) { |
| 219 | // Ensure another attach does not come in and spawn a duplicate session. |
| 220 | rpty.mutex.Lock() |
| 221 | defer rpty.mutex.Unlock() |
| 222 | |
| 223 | logger.Debug(ctx, "spawning screen client", slog.F("screen_id", rpty.id)) |
| 224 | |
| 225 | // Wrap the command with screen and tie it to the connection's context. |
| 226 | cmd := rpty.execer.PTYCommandContext(ctx, "screen", append([]string{ |
| 227 | // -S is for setting the session's name. |
| 228 | "-S", rpty.id, |
| 229 | // -U tells screen to use UTF-8 encoding. |
| 230 | // -x allows attaching to an already attached session. |
| 231 | // -RR reattaches to the daemon or creates the session daemon if missing. |
| 232 | // -q disables the "New screen..." message that appears for five seconds |
| 233 | // when creating a new session with -RR. |
| 234 | // -c is the flag for the config file. |
| 235 | "-UxRRqc", rpty.configFile, |
| 236 | rpty.command.Path, |
| 237 | // pty.Cmd duplicates Path as the first argument so remove it. |
| 238 | }, rpty.command.Args[1:]...)...) |
| 239 | cmd.Env = withTerminalEnv(rpty.command.Env) |
| 240 | cmd.Dir = rpty.command.Dir |
| 241 | ptty, process, err := pty.Start(cmd, pty.WithPTYOption( |
| 242 | pty.WithSSHRequest(ssh.Pty{ |
| 243 | Window: ssh.Window{ |
| 244 | // Make sure to spawn at the right size because if we resize afterward it |
| 245 | // leaves confusing padding (screen will resize such that the screen |
| 246 | // contents are aligned to the bottom). |
| 247 | Height: int(height), |
| 248 | Width: int(width), |
| 249 | }, |
| 250 | }), |
| 251 | )) |
| 252 | if err != nil { |
| 253 | rpty.metrics.WithLabelValues("screen_spawn").Add(1) |
| 254 | return nil, nil, err |
| 255 | } |
| 256 | |
| 257 | // This context lets us abort the version command if the process dies. |
| 258 | versionCtx, versionCancel := context.WithCancel(ctx) |
| 259 | defer versionCancel() |
| 260 | |
| 261 | // Pipe pty -> conn and close the connection when the process exits. |
| 262 | // We do not need to separately monitor for the process exiting. When it |
| 263 | // exits, our ptty.OutputReader() will return EOF after reading all process |
| 264 | // output. |
| 265 | go func() { |
| 266 | defer versionCancel() |
| 267 | defer func() { |
| 268 | err := conn.Close() |
| 269 | if err != nil { |
| 270 | // Log only for debugging since the connection might have already closed |
| 271 | // on its own. |
| 272 | logger.Debug(ctx, "closed connection with error", slog.Error(err)) |
| 273 | } |
| 274 | }() |
| 275 | buffer := make([]byte, 1024) |
no test coverage detected