detached_shell/
pty.rs

1use std::fs::File;
2use std::io::{self, Read, Write};
3use std::os::unix::io::{BorrowedFd, FromRawFd, RawFd};
4use std::os::unix::net::{UnixListener, UnixStream};
5use std::path::PathBuf;
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::sync::Arc;
8use std::thread;
9
10use crossterm::terminal;
11use nix::fcntl::{fcntl, FcntlArg, OFlag};
12use nix::sys::signal::{kill, Signal};
13use nix::sys::termios::{tcgetattr, tcsetattr, SetArg};
14use nix::unistd::{close, dup2, execvp, fork, setsid, ForkResult, Pid};
15
16use crate::error::{NdsError, Result};
17use crate::manager::SessionManager;
18use crate::pty_buffer::PtyBuffer;
19use crate::scrollback::ScrollbackViewer;
20use crate::session::Session;
21use crate::terminal_state::TerminalState;
22
23pub struct PtyProcess {
24    pub master_fd: RawFd,
25    pub pid: Pid,
26    pub socket_path: PathBuf,
27    listener: Option<UnixListener>,
28    output_buffer: Option<PtyBuffer>,
29}
30
31impl PtyProcess {
32    pub fn spawn_new_detached(session_id: &str) -> Result<Session> {
33        Self::spawn_new_detached_with_name(session_id, None)
34    }
35
36    pub fn spawn_new_detached_with_name(session_id: &str, name: Option<String>) -> Result<Session> {
37        // Capture terminal size BEFORE detaching
38        let (cols, rows) = terminal::size().unwrap_or((80, 24));
39
40        // First fork to create intermediate process
41        match unsafe { fork() }
42            .map_err(|e| NdsError::ForkError(format!("First fork failed: {}", e)))?
43        {
44            ForkResult::Parent { child: _ } => {
45                // Wait for the intermediate process to complete
46                std::thread::sleep(std::time::Duration::from_millis(200));
47
48                // Load the session that was created by the daemon
49                Session::load(session_id)
50            }
51            ForkResult::Child => {
52                // We're in the intermediate process
53                // Create a new session to detach from the terminal
54                setsid().map_err(|e| NdsError::ProcessError(format!("setsid failed: {}", e)))?;
55
56                // Second fork to ensure we can't acquire a controlling terminal
57                match unsafe { fork() }
58                    .map_err(|e| NdsError::ForkError(format!("Second fork failed: {}", e)))?
59                {
60                    ForkResult::Parent { child: _ } => {
61                        // Intermediate process exits immediately
62                        std::process::exit(0);
63                    }
64                    ForkResult::Child => {
65                        // We're now in the daemon process
66                        // Close standard file descriptors to fully detach
67                        unsafe {
68                            libc::close(0);
69                            libc::close(1);
70                            libc::close(2);
71
72                            // Redirect to /dev/null
73                            let dev_null = libc::open(
74                                b"/dev/null\0".as_ptr() as *const libc::c_char,
75                                libc::O_RDWR,
76                            );
77                            if dev_null >= 0 {
78                                libc::dup2(dev_null, 0);
79                                libc::dup2(dev_null, 1);
80                                libc::dup2(dev_null, 2);
81                                if dev_null > 2 {
82                                    libc::close(dev_null);
83                                }
84                            }
85                        }
86
87                        // Continue with PTY setup, passing the captured terminal size
88                        let (pty_process, _session) =
89                            Self::spawn_new_internal_with_size(session_id, name, cols, rows)?;
90
91                        // Run the PTY handler
92                        if let Err(_e) = pty_process.run_detached() {
93                            // Can't print errors anymore since stdout is closed
94                        }
95
96                        // Clean up when done
97                        Session::cleanup(session_id).ok();
98                        std::process::exit(0);
99                    }
100                }
101            }
102        }
103    }
104
105    fn spawn_new_internal_with_size(
106        session_id: &str,
107        name: Option<String>,
108        cols: u16,
109        rows: u16,
110    ) -> Result<(Self, Session)> {
111        // Open PTY using libc directly since nix 0.29 doesn't have pty module
112        let (master_fd, slave_fd) = Self::open_pty()?;
113
114        // Set terminal size on slave
115        unsafe {
116            let winsize = libc::winsize {
117                ws_row: rows,
118                ws_col: cols,
119                ws_xpixel: 0,
120                ws_ypixel: 0,
121            };
122            if libc::ioctl(slave_fd, libc::TIOCSWINSZ as u64, &winsize) < 0 {
123                return Err(NdsError::PtyError(
124                    "Failed to set terminal size".to_string(),
125                ));
126            }
127        }
128
129        // Set non-blocking on master
130        let flags = fcntl(master_fd, FcntlArg::F_GETFL)
131            .map_err(|e| NdsError::PtyError(format!("Failed to get flags: {}", e)))?;
132        fcntl(
133            master_fd,
134            FcntlArg::F_SETFL(OFlag::from_bits_truncate(flags) | OFlag::O_NONBLOCK),
135        )
136        .map_err(|e| NdsError::PtyError(format!("Failed to set non-blocking: {}", e)))?;
137
138        // Create socket for IPC
139        let socket_path = Session::socket_dir()?.join(format!("{}.sock", session_id));
140
141        // Remove socket if it exists
142        if socket_path.exists() {
143            std::fs::remove_file(&socket_path)?;
144        }
145
146        let listener = UnixListener::bind(&socket_path)
147            .map_err(|e| NdsError::SocketError(format!("Failed to bind socket: {}", e)))?;
148
149        // Fork process
150        match unsafe { fork() }.map_err(|e| NdsError::ForkError(e.to_string()))? {
151            ForkResult::Parent { child } => {
152                // Close slave in parent
153                let _ = close(slave_fd);
154
155                // Create session metadata
156                let session = Session::with_name(
157                    session_id.to_string(),
158                    name,
159                    child.as_raw(),
160                    socket_path.clone(),
161                );
162                session.save().map_err(|e| {
163                    eprintln!("Failed to save session: {}", e);
164                    e
165                })?;
166
167                let pty_process = PtyProcess {
168                    master_fd,
169                    pid: child,
170                    socket_path,
171                    listener: Some(listener),
172                    output_buffer: Some(PtyBuffer::new(1024 * 1024)), // 1MB buffer
173                };
174
175                Ok((pty_process, session))
176            }
177            ForkResult::Child => {
178                // Close master in child
179                let _ = close(master_fd);
180
181                // Create new session
182                setsid().map_err(|e| NdsError::ProcessError(format!("setsid failed: {}", e)))?;
183
184                // Make slave the controlling terminal
185                unsafe {
186                    if libc::ioctl(slave_fd, libc::TIOCSCTTY as u64, 0) < 0 {
187                        eprintln!("Failed to set controlling terminal");
188                        std::process::exit(1);
189                    }
190                }
191
192                // Duplicate slave to stdin/stdout/stderr
193                dup2(slave_fd, 0)
194                    .map_err(|e| NdsError::ProcessError(format!("dup2 stdin failed: {}", e)))?;
195                dup2(slave_fd, 1)
196                    .map_err(|e| NdsError::ProcessError(format!("dup2 stdout failed: {}", e)))?;
197                dup2(slave_fd, 2)
198                    .map_err(|e| NdsError::ProcessError(format!("dup2 stderr failed: {}", e)))?;
199
200                // Close original slave
201                if slave_fd > 2 {
202                    let _ = close(slave_fd);
203                }
204
205                // Get shell
206                let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
207
208                // Execute shell
209                let shell_cstr = std::ffi::CString::new(shell.as_str()).unwrap();
210                let args = vec![shell_cstr.clone()];
211
212                execvp(&shell_cstr, &args)
213                    .map_err(|e| NdsError::ProcessError(format!("execvp failed: {}", e)))?;
214
215                // Should never reach here
216                unreachable!()
217            }
218        }
219    }
220
221    fn open_pty() -> Result<(RawFd, RawFd)> {
222        unsafe {
223            // Open PTY master
224            let master_fd = libc::posix_openpt(libc::O_RDWR | libc::O_NOCTTY);
225            if master_fd < 0 {
226                return Err(NdsError::PtyError("Failed to open PTY master".to_string()));
227            }
228
229            // Grant access to slave
230            if libc::grantpt(master_fd) < 0 {
231                let _ = libc::close(master_fd);
232                return Err(NdsError::PtyError("Failed to grant PTY access".to_string()));
233            }
234
235            // Unlock slave
236            if libc::unlockpt(master_fd) < 0 {
237                let _ = libc::close(master_fd);
238                return Err(NdsError::PtyError("Failed to unlock PTY".to_string()));
239            }
240
241            // Get slave name
242            let slave_name = libc::ptsname(master_fd);
243            if slave_name.is_null() {
244                let _ = libc::close(master_fd);
245                return Err(NdsError::PtyError(
246                    "Failed to get PTY slave name".to_string(),
247                ));
248            }
249
250            // Open slave
251            let slave_cstr = std::ffi::CStr::from_ptr(slave_name);
252            let slave_fd = libc::open(slave_cstr.as_ptr(), libc::O_RDWR);
253            if slave_fd < 0 {
254                let _ = libc::close(master_fd);
255                return Err(NdsError::PtyError("Failed to open PTY slave".to_string()));
256            }
257
258            Ok((master_fd, slave_fd))
259        }
260    }
261
262    pub fn attach_to_session(session: &Session) -> Result<Option<String>> {
263        // Save current terminal state
264        let stdin_fd = 0;
265        let stdin = unsafe { BorrowedFd::borrow_raw(stdin_fd) };
266        let original_termios = tcgetattr(&stdin).map_err(|e| {
267            NdsError::TerminalError(format!("Failed to get terminal attributes: {}", e))
268        })?;
269
270        // Capture current terminal state
271        let _terminal_state = TerminalState::capture(stdin_fd)?;
272
273        // Connect to session socket
274        let mut socket = session.connect_socket()?;
275
276        // Get current terminal size and send resize command
277        let (cols, rows) = terminal::size().map_err(|e| NdsError::TerminalError(e.to_string()))?;
278
279        // Send a special resize command to the daemon
280        // Format: \x1b]nds:resize:<cols>:<rows>\x07
281        let resize_cmd = format!("\x1b]nds:resize:{}:{}\x07", cols, rows);
282        let _ = socket.write_all(resize_cmd.as_bytes());
283        let _ = socket.flush();
284
285        // Small delay to let the resize happen
286        thread::sleep(std::time::Duration::from_millis(50));
287
288        // Send a sequence to help restore the terminal state
289        // First, send Ctrl+L to refresh the display
290        let _ = socket.write_all(b"\x0c");
291        let _ = socket.flush();
292
293        // Small delay to let the refresh happen
294        thread::sleep(std::time::Duration::from_millis(50));
295
296        // Set terminal to raw mode
297        let mut raw = original_termios.clone();
298        // Manually set raw mode flags
299        raw.input_flags = nix::sys::termios::InputFlags::empty();
300        raw.output_flags = nix::sys::termios::OutputFlags::empty();
301        raw.control_flags |= nix::sys::termios::ControlFlags::CS8;
302        raw.local_flags = nix::sys::termios::LocalFlags::empty();
303        raw.control_chars[nix::sys::termios::SpecialCharacterIndices::VMIN as usize] = 1;
304        raw.control_chars[nix::sys::termios::SpecialCharacterIndices::VTIME as usize] = 0;
305        tcsetattr(&stdin, SetArg::TCSANOW, &raw)
306            .map_err(|e| NdsError::TerminalError(format!("Failed to set raw mode: {}", e)))?;
307
308        // Create a flag for clean shutdown
309        let running = Arc::new(AtomicBool::new(true));
310        let r1 = running.clone();
311        let r2 = running.clone();
312
313        // Handle Ctrl+C
314        ctrlc::set_handler(move || {
315            r1.store(false, Ordering::SeqCst);
316        })
317        .map_err(|e| NdsError::SignalError(format!("Failed to set signal handler: {}", e)))?;
318
319        println!("\r\n[Attached to session {}]\r", session.id);
320        println!("[Press Enter then ~d to detach, ~s to switch, ~h for history]\r");
321
322        // Scrollback buffer (max 10MB)
323        let scrollback_buffer = Arc::new(std::sync::Mutex::new(Vec::<u8>::new()));
324
325        // Spawn thread to monitor terminal size changes
326        let socket_for_resize = socket
327            .try_clone()
328            .map_err(|e| NdsError::SocketError(format!("Failed to clone socket: {}", e)))?;
329        let resize_running = running.clone();
330        let _resize_monitor = thread::spawn(move || {
331            let mut last_size = (cols, rows);
332            let mut socket = socket_for_resize;
333
334            while resize_running.load(Ordering::SeqCst) {
335                if let Ok((new_cols, new_rows)) = terminal::size() {
336                    if (new_cols, new_rows) != last_size {
337                        // Terminal size changed, send resize command
338                        let resize_cmd = format!("\x1b]nds:resize:{}:{}\x07", new_cols, new_rows);
339                        let _ = socket.write_all(resize_cmd.as_bytes());
340                        let _ = socket.flush();
341                        last_size = (new_cols, new_rows);
342                    }
343                }
344                thread::sleep(std::time::Duration::from_millis(250));
345            }
346        });
347
348        // Spawn thread to read from socket and write to stdout
349        let socket_clone = socket
350            .try_clone()
351            .map_err(|e| NdsError::SocketError(format!("Failed to clone socket: {}", e)))?;
352        let scrollback_clone = Arc::clone(&scrollback_buffer);
353        let socket_to_stdout = thread::spawn(move || {
354            let mut socket = socket_clone;
355            let mut stdout = io::stdout();
356            let mut buffer = [0u8; 4096];
357
358            while r2.load(Ordering::SeqCst) {
359                match socket.read(&mut buffer) {
360                    Ok(0) => break, // Socket closed
361                    Ok(n) => {
362                        // Write to stdout
363                        if let Err(_) = stdout.write_all(&buffer[..n]) {
364                            break;
365                        }
366                        let _ = stdout.flush();
367
368                        // Add to scrollback buffer
369                        let mut scrollback = scrollback_clone.lock().unwrap();
370                        scrollback.extend_from_slice(&buffer[..n]);
371
372                        // Trim if too large
373                        let scrollback_max = 10 * 1024 * 1024;
374                        if scrollback.len() > scrollback_max {
375                            let remove = scrollback.len() - scrollback_max;
376                            scrollback.drain(..remove);
377                        }
378                    }
379                    Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
380                        thread::sleep(std::time::Duration::from_millis(10));
381                    }
382                    Err(ref e) if e.kind() == io::ErrorKind::BrokenPipe => {
383                        // Expected when socket is closed, just exit cleanly
384                        break;
385                    }
386                    Err(_) => break,
387                }
388            }
389        });
390
391        // Read from stdin and write to socket
392        let mut stdin = io::stdin();
393        let mut buffer = [0u8; 1024];
394
395        // SSH-style escape sequence: Enter ~d
396        // We'll track if we're at the beginning of a line
397        let mut at_line_start = true;
398        let mut escape_state = 0; // 0=normal, 1=saw tilde at line start
399        let mut escape_time = std::time::Instant::now();
400
401        loop {
402            if !running.load(Ordering::SeqCst) {
403                break;
404            }
405
406            match stdin.read(&mut buffer) {
407                Ok(0) => break,
408                Ok(n) => {
409                    let mut should_detach = false;
410                    let mut should_switch = false;
411                    let mut should_scroll = false;
412                    let mut data_to_forward = Vec::new();
413
414                    // Check for escape timeout (reset after 1 second)
415                    if escape_state == 1
416                        && escape_time.elapsed() > std::time::Duration::from_secs(1)
417                    {
418                        // Timeout - forward the held tilde and reset
419                        data_to_forward.push(b'~');
420                        escape_state = 0;
421                    }
422
423                    // Process each byte for escape sequence
424                    for i in 0..n {
425                        let byte = buffer[i];
426
427                        match escape_state {
428                            0 => {
429                                // Normal state
430                                if at_line_start && byte == b'~' {
431                                    // Start of potential escape sequence
432                                    escape_state = 1;
433                                    escape_time = std::time::Instant::now();
434                                    // Don't forward the tilde yet
435                                } else {
436                                    // Regular character
437                                    data_to_forward.push(byte);
438                                    // Update line start tracking
439                                    at_line_start =
440                                        byte == b'\r' || byte == b'\n' || byte == 10 || byte == 13;
441                                }
442                            }
443                            1 => {
444                                // We saw ~ at the beginning of a line
445                                if byte == b'd' {
446                                    // Detach command ~d
447                                    should_detach = true;
448                                    break;
449                                } else if byte == b's' {
450                                    // Switch sessions command ~s
451                                    should_switch = true;
452                                    break;
453                                } else if byte == b'h' {
454                                    // History/scrollback command ~h
455                                    should_scroll = true;
456                                    break;
457                                } else if byte == b'~' {
458                                    // ~~ means literal tilde
459                                    data_to_forward.push(b'~');
460                                    escape_state = 0;
461                                    at_line_start = false;
462                                } else {
463                                    // Not an escape sequence, forward tilde and this char
464                                    data_to_forward.push(b'~');
465                                    data_to_forward.push(byte);
466                                    escape_state = 0;
467                                    at_line_start =
468                                        byte == b'\r' || byte == b'\n' || byte == 10 || byte == 13;
469                                }
470                            }
471                            _ => {
472                                escape_state = 0;
473                            }
474                        }
475                    }
476
477                    if should_detach {
478                        println!("\r\n[Detaching from session {}]\r", session.id);
479                        break;
480                    }
481
482                    if should_switch {
483                        // Show session switcher
484                        println!("\r\n[Session Switcher]\r");
485
486                        // Get list of other sessions
487                        match SessionManager::list_sessions() {
488                            Ok(sessions) => {
489                                let other_sessions: Vec<_> =
490                                    sessions.iter().filter(|s| s.id != session.id).collect();
491
492                                // Show available sessions
493                                println!("\r\nAvailable options:\r");
494
495                                // Show existing sessions
496                                if !other_sessions.is_empty() {
497                                    for (i, s) in other_sessions.iter().enumerate() {
498                                        println!(
499                                            "\r  {}. {} (PID: {})\r",
500                                            i + 1,
501                                            s.display_name(),
502                                            s.pid
503                                        );
504                                    }
505                                }
506
507                                // Add new session option
508                                let new_option_num = other_sessions.len() + 1;
509                                println!("\r  {}. [New Session]\r", new_option_num);
510                                println!("\r  0. Cancel\r");
511                                println!("\r\nSelect option (0-{}): ", new_option_num);
512                                let _ = io::stdout().flush();
513
514                                // Read user selection
515                                let mut selection = String::new();
516                                if let Ok(_) = io::stdin().read_line(&mut selection) {
517                                    if let Ok(num) = selection.trim().parse::<usize>() {
518                                        if num > 0 && num <= other_sessions.len() {
519                                            // Switch to selected session
520                                            let target_session = other_sessions[num - 1];
521                                            println!(
522                                                "\r\n[Switching to session {}]\r",
523                                                target_session.id
524                                            );
525
526                                            // Store the target session ID for return
527                                            return Ok(Some(target_session.id.clone()));
528                                        } else if num == new_option_num {
529                                            // Create new session
530                                            println!("\r\nEnter name for new session (or press Enter for no name): ");
531                                            let _ = io::stdout().flush();
532
533                                            let mut session_name = String::new();
534                                            if let Ok(_) = io::stdin().read_line(&mut session_name)
535                                            {
536                                                let session_name = session_name.trim();
537                                                let name = if session_name.is_empty() {
538                                                    None
539                                                } else {
540                                                    Some(session_name.to_string())
541                                                };
542
543                                                // Create new session
544                                                match SessionManager::create_session_with_name(
545                                                    name.clone(),
546                                                ) {
547                                                    Ok(new_session) => {
548                                                        if let Some(ref n) = name {
549                                                            println!("\r\n[Created and switching to new session '{}' ({})]", n, new_session.id);
550                                                        } else {
551                                                            println!("\r\n[Created and switching to new session {}]", new_session.id);
552                                                        }
553
554                                                        // Return the new session ID to switch to it
555                                                        return Ok(Some(new_session.id.clone()));
556                                                    }
557                                                    Err(e) => {
558                                                        eprintln!(
559                                                            "\r\nError creating session: {}\r",
560                                                            e
561                                                        );
562                                                    }
563                                                }
564                                            }
565                                        }
566                                    }
567                                }
568
569                                // Cancelled or invalid selection
570                                println!("\r\n[Continuing current session]\r");
571                                escape_state = 0;
572                                at_line_start = true;
573                            }
574                            Err(e) => {
575                                eprintln!("\r\nError listing sessions: {}\r", e);
576                                escape_state = 0;
577                                at_line_start = true;
578                            }
579                        }
580                    }
581
582                    if should_scroll {
583                        // Show scrollback viewer
584                        println!("\r\n[Opening scrollback viewer...]\r");
585
586                        // Get scrollback content
587                        let content = scrollback_buffer.lock().unwrap().clone();
588
589                        // Temporarily restore terminal for viewer
590                        let stdin_fd = 0;
591                        let stdin = unsafe { BorrowedFd::borrow_raw(stdin_fd) };
592                        tcsetattr(&stdin, SetArg::TCSANOW, &original_termios).map_err(|e| {
593                            NdsError::TerminalError(format!("Failed to restore terminal: {}", e))
594                        })?;
595
596                        // Show scrollback viewer
597                        let mut viewer = ScrollbackViewer::new(&content);
598                        let _ = viewer.run(); // Ignore errors, just return to session
599
600                        // Re-enter raw mode
601                        tcsetattr(&stdin, SetArg::TCSANOW, &raw).map_err(|e| {
602                            NdsError::TerminalError(format!("Failed to set raw mode: {}", e))
603                        })?;
604
605                        // Refresh display
606                        let _ = socket.write_all(b"\x0c"); // Ctrl+L
607                        let _ = socket.flush();
608
609                        println!("\r\n[Returned to session]\r");
610
611                        // Reset state
612                        escape_state = 0;
613                        at_line_start = true;
614                    }
615
616                    // Forward the processed data
617                    if !data_to_forward.is_empty() {
618                        if let Err(e) = socket.write_all(&data_to_forward) {
619                            // Check if it's a broken pipe (expected on detach)
620                            if e.kind() == io::ErrorKind::BrokenPipe {
621                                // This is expected when detaching, just break
622                                break;
623                            } else {
624                                eprintln!("\r\nError writing to socket: {}\r", e);
625                                break;
626                            }
627                        }
628                    }
629                }
630                Err(e) => {
631                    eprintln!("\r\nError reading stdin: {}\r", e);
632                    break;
633                }
634            }
635        }
636
637        // Stop the socket reader thread
638        running.store(false, Ordering::SeqCst);
639
640        // Close the socket to unblock the reader thread
641        drop(socket);
642
643        // Wait for the thread with a timeout
644        thread::sleep(std::time::Duration::from_millis(100));
645        let _ = socket_to_stdout.join();
646
647        // Restore terminal - do this BEFORE any output
648        let stdin_fd = 0;
649        let stdin = unsafe { BorrowedFd::borrow_raw(stdin_fd) };
650
651        // First restore the terminal settings
652        tcsetattr(&stdin, SetArg::TCSANOW, &original_termios)
653            .map_err(|e| NdsError::TerminalError(format!("Failed to restore terminal: {}", e)))?;
654
655        // Ensure we're back in cooked mode
656        terminal::disable_raw_mode().ok();
657
658        // Add a small delay to ensure terminal is fully restored
659        thread::sleep(std::time::Duration::from_millis(50));
660
661        // Now it's safe to print the detach message
662        println!("\n[Detached from session {}]", session.id);
663
664        // Flush stdout to ensure message is displayed
665        let _ = io::stdout().flush();
666
667        Ok(None)
668    }
669
670    pub fn run_detached(mut self) -> Result<()> {
671        let listener = self
672            .listener
673            .take()
674            .ok_or_else(|| NdsError::PtyError("No listener available".to_string()))?;
675
676        // Set listener to non-blocking
677        listener.set_nonblocking(true)?;
678
679        let running = Arc::new(AtomicBool::new(true));
680        let r = running.clone();
681
682        // Handle cleanup on exit
683        ctrlc::set_handler(move || {
684            r.store(false, Ordering::SeqCst);
685        })
686        .map_err(|e| NdsError::SignalError(format!("Failed to set signal handler: {}", e)))?;
687
688        let output_buffer = self
689            .output_buffer
690            .take()
691            .ok_or_else(|| NdsError::PtyError("No output buffer available".to_string()))?;
692
693        // Support multiple concurrent clients
694        let mut active_clients: Vec<UnixStream> = Vec::new();
695        let mut buffer = [0u8; 4096];
696
697        // Get session ID from socket path
698        let session_id = self
699            .socket_path
700            .file_stem()
701            .and_then(|s| s.to_str())
702            .unwrap_or("unknown")
703            .to_string();
704
705        while running.load(Ordering::SeqCst) {
706            // Check for new connections
707            match listener.accept() {
708                Ok((mut stream, _)) => {
709                    stream.set_nonblocking(true)?;
710
711                    // Notify existing clients about new connection
712                    let notification = format!(
713                        "\r\n[Another client connected to this session (total: {})]\r\n",
714                        active_clients.len() + 1
715                    );
716                    for client in &mut active_clients {
717                        let _ = client.write_all(notification.as_bytes());
718                        let _ = client.flush();
719                    }
720
721                    // Send buffered output to new client
722                    if !output_buffer.is_empty() {
723                        let mut buffered_data = Vec::new();
724                        output_buffer.drain_to(&mut buffered_data);
725
726                        // Save cursor position, clear screen, and reset
727                        let init_sequence = b"\x1b7\x1b[?47h\x1b[2J\x1b[H"; // Save cursor, alt screen, clear, home
728                        let _ = stream.write_all(init_sequence);
729                        let _ = stream.flush();
730
731                        // Send buffered data in chunks to avoid overwhelming the client
732                        for chunk in buffered_data.chunks(4096) {
733                            let _ = stream.write_all(chunk);
734                            let _ = stream.flush();
735                            std::thread::sleep(std::time::Duration::from_millis(1));
736                        }
737
738                        // Exit alt screen and restore cursor
739                        let restore_sequence = b"\x1b[?47l\x1b8"; // Exit alt screen, restore cursor
740                        let _ = stream.write_all(restore_sequence);
741                        let _ = stream.flush();
742
743                        // Small delay for terminal to process
744                        std::thread::sleep(std::time::Duration::from_millis(50));
745
746                        // Send a full redraw command to the shell
747                        let mut master_file = unsafe { File::from_raw_fd(self.master_fd) };
748                        let _ = master_file.write_all(b"\x0c"); // Ctrl+L to refresh
749                        std::mem::forget(master_file); // Don't close the fd
750
751                        // Give time for the refresh to complete
752                        std::thread::sleep(std::time::Duration::from_millis(100));
753                    } else {
754                        // No buffer, just request a refresh to sync state
755                        let mut master_file = unsafe { File::from_raw_fd(self.master_fd) };
756                        let _ = master_file.write_all(b"\x0c"); // Ctrl+L to refresh
757                        std::mem::forget(master_file); // Don't close the fd
758                    }
759
760                    // Add new client to the list
761                    active_clients.push(stream);
762
763                    // Update client count in status file
764                    let _ = Session::update_client_count(&session_id, active_clients.len());
765                }
766                Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
767                    // No new connections
768                }
769                Err(_e) => {
770                    // Error accepting connection, continue
771                }
772            }
773
774            // Read from PTY master
775            let master_file = unsafe { File::from_raw_fd(self.master_fd) };
776            let mut master_file_clone = master_file.try_clone()?;
777            std::mem::forget(master_file); // Don't close the fd
778
779            match master_file_clone.read(&mut buffer) {
780                Ok(0) => {
781                    // Child process exited
782                    break;
783                }
784                Ok(n) => {
785                    let data = &buffer[..n];
786
787                    // Broadcast to all connected clients
788                    if !active_clients.is_empty() {
789                        let mut disconnected_indices = Vec::new();
790
791                        for (i, client) in active_clients.iter_mut().enumerate() {
792                            if let Err(e) = client.write_all(data) {
793                                if e.kind() == io::ErrorKind::BrokenPipe
794                                    || e.kind() == io::ErrorKind::ConnectionAborted
795                                {
796                                    // Mark client for removal
797                                    disconnected_indices.push(i);
798                                }
799                            } else {
800                                let _ = client.flush();
801                            }
802                        }
803
804                        // Remove disconnected clients and notify others
805                        if !disconnected_indices.is_empty() {
806                            for i in disconnected_indices.iter().rev() {
807                                active_clients.remove(*i);
808                            }
809
810                            // Update client count in status file
811                            let _ = Session::update_client_count(&session_id, active_clients.len());
812
813                            // Notify remaining clients
814                            if !active_clients.is_empty() {
815                                let notification = format!(
816                                    "\r\n[A client disconnected (remaining: {})]\r\n",
817                                    active_clients.len()
818                                );
819                                for client in &mut active_clients {
820                                    let _ = client.write_all(notification.as_bytes());
821                                    let _ = client.flush();
822                                }
823                            }
824                        }
825
826                        // If all clients disconnected, start buffering
827                        if active_clients.is_empty() {
828                            output_buffer.push(data);
829                        }
830                    } else {
831                        // No clients connected, buffer the output
832                        output_buffer.push(data);
833                    }
834                }
835                Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
836                    // No data available
837                }
838                Err(_) => {
839                    // Other error, continue
840                }
841            }
842
843            // Read from clients and write to PTY
844            let mut disconnected_indices = Vec::new();
845
846            for (i, client) in active_clients.iter_mut().enumerate() {
847                let mut client_buffer = [0u8; 1024];
848                match client.read(&mut client_buffer) {
849                    Ok(0) => {
850                        // Client disconnected
851                        disconnected_indices.push(i);
852                    }
853                    Ok(n) => {
854                        let data = &client_buffer[..n];
855
856                        // Check for special NDS commands
857                        // Format: \x1b]nds:resize:<cols>:<rows>\x07
858                        if n > 10 && data.starts_with(b"\x1b]nds:") {
859                            if let Ok(cmd_str) = std::str::from_utf8(data) {
860                                if let Some(end_idx) = cmd_str.find('\x07') {
861                                    let cmd = &cmd_str[2..end_idx]; // Skip \x1b]
862                                    if cmd.starts_with("nds:resize:") {
863                                        // Parse resize command
864                                        let parts: Vec<&str> =
865                                            cmd["nds:resize:".len()..].split(':').collect();
866                                        if parts.len() == 2 {
867                                            if let (Ok(cols), Ok(rows)) =
868                                                (parts[0].parse::<u16>(), parts[1].parse::<u16>())
869                                            {
870                                                // Resize the PTY
871                                                unsafe {
872                                                    let winsize = libc::winsize {
873                                                        ws_row: rows,
874                                                        ws_col: cols,
875                                                        ws_xpixel: 0,
876                                                        ws_ypixel: 0,
877                                                    };
878                                                    libc::ioctl(
879                                                        self.master_fd,
880                                                        libc::TIOCSWINSZ as u64,
881                                                        &winsize,
882                                                    );
883                                                }
884
885                                                // Send SIGWINCH to the child process to notify of resize
886                                                let _ = kill(self.pid, Signal::SIGWINCH);
887
888                                                // Don't forward the resize command to the PTY
889                                                // But forward any remaining data after the command
890                                                if end_idx + 1 < n {
891                                                    let remaining = &data[end_idx + 1..];
892                                                    if !remaining.is_empty() {
893                                                        let mut master_file = unsafe {
894                                                            File::from_raw_fd(self.master_fd)
895                                                        };
896                                                        let _ = master_file.write_all(remaining);
897                                                        std::mem::forget(master_file);
898                                                        // Don't close the fd
899                                                    }
900                                                }
901                                                continue; // Skip normal forwarding
902                                            }
903                                        }
904                                    }
905                                }
906                            }
907                        }
908
909                        // Normal data - forward to PTY
910                        let mut master_file = unsafe { File::from_raw_fd(self.master_fd) };
911                        let _ = master_file.write_all(data);
912                        std::mem::forget(master_file); // Don't close the fd
913                    }
914                    Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
915                        // No data available
916                    }
917                    Err(_) => {
918                        // Client error, mark for removal
919                        disconnected_indices.push(i);
920                    }
921                }
922            }
923
924            // Remove disconnected clients and notify others
925            if !disconnected_indices.is_empty() {
926                for i in disconnected_indices.iter().rev() {
927                    active_clients.remove(*i);
928                }
929
930                // Update client count in status file
931                let _ = Session::update_client_count(&session_id, active_clients.len());
932
933                // Notify remaining clients
934                if !active_clients.is_empty() {
935                    let notification = format!(
936                        "\r\n[A client disconnected (remaining: {})]\r\n",
937                        active_clients.len()
938                    );
939                    for client in &mut active_clients {
940                        let _ = client.write_all(notification.as_bytes());
941                        let _ = client.flush();
942                    }
943                }
944            }
945
946            // Small sleep to prevent busy loop
947            thread::sleep(std::time::Duration::from_millis(10));
948        }
949
950        Ok(())
951    }
952
953    pub fn kill_session(session_id: &str) -> Result<()> {
954        let session = Session::load(session_id)?;
955
956        // Send SIGTERM to the process
957        kill(Pid::from_raw(session.pid), Signal::SIGTERM)
958            .map_err(|e| NdsError::ProcessError(format!("Failed to kill process: {}", e)))?;
959
960        // Wait a moment for graceful shutdown
961        thread::sleep(std::time::Duration::from_millis(500));
962
963        // Force kill if still alive
964        if Session::is_process_alive(session.pid) {
965            kill(Pid::from_raw(session.pid), Signal::SIGKILL).map_err(|e| {
966                NdsError::ProcessError(format!("Failed to force kill process: {}", e))
967            })?;
968        }
969
970        // Clean up session files
971        Session::cleanup(session_id)?;
972
973        Ok(())
974    }
975}
976
977impl Drop for PtyProcess {
978    fn drop(&mut self) {
979        let _ = close(self.master_fd);
980        if let Some(listener) = self.listener.take() {
981            drop(listener);
982        }
983    }
984}