Skip to content

Add pause/resume support with SPACE key#625

Open
sheikhshaheerimran wants to merge 1 commit intoOJ:devfrom
sheikhshaheerimran:feat/pause-resume
Open

Add pause/resume support with SPACE key#625
sheikhshaheerimran wants to merge 1 commit intoOJ:devfrom
sheikhshaheerimran:feat/pause-resume

Conversation

@sheikhshaheerimran
Copy link

@sheikhshaheerimran sheikhshaheerimran commented Jan 1, 2026

Add runtime pause/resume functionality for scans. Press SPACE to pause, press again to resume.

  • Workers block on pause and resume when unpaused
  • Progress bar shows [PAUSED] prefix when paused
  • Keyboard listener uses blocking read with channel (no CPU polling)
  • Cross-platform support (Linux/Windows)
  • Disabled when reading wordlist from stdin or when output is piped

Closes #18

  - Add PauseController for worker synchronization
  - Add keyboard listener for SPACE key toggle
  - Show [PAUSED] in progress bar when paused
@sheikhshaheerimran
Copy link
Author

@firefart Requesting your review

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds runtime pause/resume functionality to gobuster, allowing users to pause and resume scans by pressing the SPACE key during execution. This addresses the enhancement request in issue #18.

Changes:

  • Implemented a thread-safe PauseController with channel-based synchronization for pausing/resuming workers
  • Added cross-platform keyboard listeners (Unix and Windows) with raw terminal mode for real-time key detection
  • Integrated pause functionality into worker loops with context-aware blocking
  • Enhanced progress bar to display pause status and user instructions
  • Updated Windows terminal clear sequence to use proper ANSI escape codes

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
libgobuster/pause.go Core pause controller implementation with mutex-protected state and channel-based wait/resume mechanism
libgobuster/pause_test.go Comprehensive unit tests for pause controller functionality including concurrency scenarios
libgobuster/libgobuster.go Integration of pause checks into worker loop for blocking on pause events
cli/keyboard.go Unix/Linux keyboard listener with raw terminal mode and signal handling
cli/keyboard_windows.go Windows keyboard listener with platform-specific stdin handling
cli/gobuster.go UI integration showing pause status in progress bar and starting keyboard listener conditionally
cli/const_windows.go Windows terminal clear line constant updated to proper ANSI escape sequence

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +152 to +170
func TestPauseControllerConcurrency(t *testing.T) {
t.Parallel()
pc := NewPauseController()

var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 100; j++ {
pc.Pause()
_ = pc.IsPaused()
pc.Resume()
}
}()
}

wg.Wait()
}
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The concurrency test doesn't cover the critical race condition scenario where multiple goroutines are blocked in Wait() while Resume()/Toggle() is called. This test only exercises Pause/Resume/IsPaused in a loop without any goroutines waiting.

Add a test case where multiple goroutines call Wait() and are blocked, then call Toggle()/Resume() to ensure all waiting goroutines are properly unblocked.

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +85
func StartKeyboardListener(ctx context.Context, g *libgobuster.Gobuster, cancel context.CancelFunc) func() {
fd := int(os.Stdin.Fd())
oldState, err := term.MakeRaw(fd)
if err != nil {
return func() {}
}

var restoreOnce sync.Once
restoreTerminal := func() {
restoreOnce.Do(func() {
_ = term.Restore(fd, oldState)
})
}

// restore terminal on signals
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)

go func() {
select {
case sig := <-sigCh:
restoreTerminal()
signal.Stop(sigCh)
// re-raise signal
p, _ := os.FindProcess(os.Getpid())
_ = p.Signal(sig.(syscall.Signal))
case <-ctx.Done():
signal.Stop(sigCh)
}
}()

// read keys and send to channel
keyCh := make(chan byte, 1)
go func() {
buf := make([]byte, 1)
for {
n, err := os.Stdin.Read(buf)
if err != nil || n == 0 {
return
}
select {
case keyCh <- buf[0]:
case <-ctx.Done():
return
}
}
}()

// handle key events
go func() {
for {
select {
case <-ctx.Done():
return
case key := <-keyCh:
if key == ' ' {
g.Pause.Toggle()
}
// Ctrl+C
if key == 3 {
restoreTerminal()
cancel()
return
}
}
}
}()

return restoreTerminal
}
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The keyboard listener functionality lacks test coverage. Consider adding tests to verify:

  • Correct handling of SPACE key for pause/resume toggle
  • Correct handling of Ctrl+C
  • Terminal state restoration on various exit paths
  • Goroutine cleanup on context cancellation

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +83
func StartKeyboardListener(ctx context.Context, g *libgobuster.Gobuster, cancel context.CancelFunc) func() {
fd := int(syscall.Stdin)
oldState, err := term.MakeRaw(fd)
if err != nil {
return func() {}
}

var restoreOnce sync.Once
restoreTerminal := func() {
restoreOnce.Do(func() {
_ = term.Restore(fd, oldState)
})
}

// restore terminal on signals
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)

go func() {
select {
case <-sigCh:
restoreTerminal()
signal.Stop(sigCh)
cancel()
case <-ctx.Done():
signal.Stop(sigCh)
}
}()

// read keys and send to channel
keyCh := make(chan byte, 1)
go func() {
buf := make([]byte, 1)
for {
n, err := os.Stdin.Read(buf)
if err != nil || n == 0 {
return
}
select {
case keyCh <- buf[0]:
case <-ctx.Done():
return
}
}
}()

// handle key events
go func() {
for {
select {
case <-ctx.Done():
return
case key := <-keyCh:
if key == ' ' {
g.Pause.Toggle()
}
// Ctrl+C
if key == 3 {
restoreTerminal()
cancel()
return
}
}
}
}()

return restoreTerminal
}
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The keyboard listener functionality lacks test coverage. Consider adding tests to verify:

  • Correct handling of SPACE key for pause/resume toggle
  • Correct handling of Ctrl+C
  • Terminal state restoration on various exit paths
  • Goroutine cleanup on context cancellation

Copilot uses AI. Check for mistakes.
g.Pause.Toggle()
}
// Ctrl+C
if key == 3 {
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The magic number 3 for Ctrl+C detection lacks clarity. Consider defining a named constant such as const ctrlC = 3 to make the code more maintainable and self-documenting.

Copilot uses AI. Check for mistakes.
g.Pause.Toggle()
}
// Ctrl+C
if key == 3 {
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The magic number 3 for Ctrl+C detection lacks clarity. Consider defining a named constant such as const ctrlC = 3 to make the code more maintainable and self-documenting.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant