sendCommand runs a screen command against a running screen session. If the command fails with an error matching anything in successErrors it will be considered a success state (for example "no session" when quitting and the session is already dead). The command will be retried until successful, th
(ctx context.Context, command string, successErrors []string)
| 336 | // canceled context's error as-is while a timed-out context returns together |
| 337 | // with the last error from the command. |
| 338 | func (rpty *screenReconnectingPTY) sendCommand(ctx context.Context, command string, successErrors []string) error { |
| 339 | ctx, cancel := context.WithTimeout(ctx, attachTimeout) |
| 340 | defer cancel() |
| 341 | |
| 342 | var lastErr error |
| 343 | run := func() (bool, error) { |
| 344 | var stdout bytes.Buffer |
| 345 | //nolint:gosec |
| 346 | cmd := rpty.execer.CommandContext(ctx, "screen", |
| 347 | // -x targets an attached session. |
| 348 | "-x", rpty.id, |
| 349 | // -c is the flag for the config file. |
| 350 | "-c", rpty.configFile, |
| 351 | // -X runs a command in the matching session. |
| 352 | "-X", command, |
| 353 | ) |
| 354 | cmd.Env = withTerminalEnv(rpty.command.Env) |
| 355 | cmd.Dir = rpty.command.Dir |
| 356 | cmd.Stdout = &stdout |
| 357 | err := cmd.Run() |
| 358 | if err == nil { |
| 359 | return true, nil |
| 360 | } |
| 361 | |
| 362 | stdoutStr := stdout.String() |
| 363 | for _, se := range successErrors { |
| 364 | if strings.Contains(stdoutStr, se) { |
| 365 | return true, nil |
| 366 | } |
| 367 | } |
| 368 | |
| 369 | // Things like "exit status 1" are imprecise so include stdout as it may |
| 370 | // contain more information ("no screen session found" for example). |
| 371 | if !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) { |
| 372 | lastErr = xerrors.Errorf("`screen -x %s -X %s`: %w: %s", rpty.id, command, err, stdoutStr) |
| 373 | } |
| 374 | |
| 375 | return false, nil |
| 376 | } |
| 377 | |
| 378 | // Run immediately. |
| 379 | done, err := run() |
| 380 | if err != nil { |
| 381 | return err |
| 382 | } |
| 383 | if done { |
| 384 | return nil |
| 385 | } |
| 386 | |
| 387 | // Then run on an interval. |
| 388 | ticker := time.NewTicker(250 * time.Millisecond) |
| 389 | defer ticker.Stop() |
| 390 | |
| 391 | for { |
| 392 | select { |
| 393 | case <-ctx.Done(): |
| 394 | if errors.Is(ctx.Err(), context.Canceled) { |
| 395 | return ctx.Err() |