// Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later package control import ( "bytes" "fmt" "os" "path/filepath" "strconv" "strings" "syscall" ) // A Screen server keeps its original environment after reattachment. Locate // its current display processes through the server's open terminal devices, // then use their reader ancestry. No Screen commands or init services are used. // procRoot is explicit so device, ownership and ambiguity cases can be tested. func attached_screen_reader(procRoot string, ancestors []int, uid int, readers []instance) (instance, bool, error) { for _, pid := range ancestors { processPath := filepath.Join(procRoot, strconv.Itoa(pid)) info, err := os.Stat(processPath) if err != nil || !owned_by(info, uid) { continue } command, err := os.ReadFile(filepath.Join(processPath, "cmdline")) if err != nil || filepath.Base(strings.TrimSpace(string(bytes.SplitN(command, []byte{0}, 2)[0]))) != "SCREEN" { continue } executablePath, err := os.Readlink(filepath.Join(processPath, "exe")) name := strings.TrimSuffix(filepath.Base(executablePath), " (deleted)") if err != nil || (name != "screen" && !strings.HasPrefix(name, "screen-")) { continue } executable, err := os.Stat(filepath.Join(processPath, "exe")) if err != nil { continue } reader, err := screen_display_reader(procRoot, processPath, uid, executable, readers) return reader, true, err } return instance{}, false, nil } func owned_by(info os.FileInfo, uid int) bool { stat, ok := info.Sys().(*syscall.Stat_t) return ok && int(stat.Uid) == uid } func screen_display_reader(procRoot, serverPath string, uid int, executable os.FileInfo, readers []instance) (instance, error) { fdPath := filepath.Join(serverPath, "fd") files, err := os.ReadDir(fdPath) if err != nil { return instance{}, fmt.Errorf("cannot inspect Screen attachments: %w", err) } displays := map[uint32]bool{} for _, file := range files { path := filepath.Join(fdPath, file.Name()) info, err := os.Stat(path) if err != nil || info.Mode()&os.ModeCharDevice == 0 { continue } // /proc/PID/stat encodes tty_nr as a signed 32-bit device number. stat, ok := info.Sys().(*syscall.Stat_t) if ok && stat.Rdev != 0 { displays[uint32(stat.Rdev)] = true } } processes, err := os.ReadDir(procRoot) if err != nil { return instance{}, fmt.Errorf("cannot inspect Screen display processes: %w", err) } matches := map[int]instance{} for _, process := range processes { pid, err := strconv.Atoi(process.Name()) if err != nil || pid <= 1 { continue } path := filepath.Join(procRoot, process.Name()) info, err := os.Stat(path) if err != nil || !owned_by(info, uid) { continue } data, err := os.ReadFile(filepath.Join(path, "stat")) if err != nil { continue } _, tty := process_parent_tty(data) if tty == 0 || !displays[tty] { continue } info, err = os.Stat(filepath.Join(path, "exe")) if err != nil || !os.SameFile(info, executable) { continue } // Display clients and the server run the same Screen executable. // The daemon has no controlling terminal; display clients have one. seen := map[int]bool{} for ancestor := pid; ancestor > 1 && !seen[ancestor]; { seen[ancestor] = true for _, reader := range readers { if reader.PID == ancestor { matches[reader.PID] = reader ancestor = 0 break } } if ancestor == 0 { break } data, err := os.ReadFile(filepath.Join(procRoot, strconv.Itoa(ancestor), "stat")) if err != nil { break } ancestor, _ = process_parent_tty(data) } } if len(matches) == 1 { for _, reader := range matches { return reader, nil } } return instance{}, fmt.Errorf("Screen session does not identify a unique attached reader; use its private socket") } func process_parent_tty(data []byte) (int, uint32) { end := bytes.LastIndexByte(data, ')') if end < 0 { return 0, 0 } fields := strings.Fields(string(data[end+1:])) if len(fields) < 5 { return 0, 0 } parent, _ := strconv.Atoi(fields[1]) tty, _ := strconv.ParseInt(fields[4], 10, 64) return parent, uint32(tty) }