203 lines
4.1 KiB
Go
203 lines
4.1 KiB
Go
package welcome
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"bash_go_service/shared/pkg/constants"
|
|
"bash_go_service/shared/pkg/logger"
|
|
|
|
"github.com/fsnotify/fsnotify"
|
|
)
|
|
|
|
type Service struct {
|
|
watcher *fsnotify.Watcher
|
|
sigChan chan os.Signal
|
|
stopChan chan struct{}
|
|
}
|
|
|
|
func NewService() (*Service, error) {
|
|
watcher, err := fsnotify.NewWatcher()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Service{
|
|
watcher: watcher,
|
|
sigChan: make(chan os.Signal, 1),
|
|
stopChan: make(chan struct{}),
|
|
}, nil
|
|
}
|
|
|
|
func (s *Service) checkExistingTerminals() {
|
|
entries, err := os.ReadDir(constants.UtmpDir)
|
|
if err != nil {
|
|
logger.Error("Error reading %s: %v", constants.UtmpDir, err)
|
|
return
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
if !entry.IsDir() {
|
|
path := filepath.Join(constants.UtmpDir, entry.Name())
|
|
if isValidTerminal(path) {
|
|
go s.handleNewTerminal(path)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Service) Start() error {
|
|
err := s.watcher.Add(constants.UtmpDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
signal.Notify(s.sigChan, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
s.checkExistingTerminals()
|
|
|
|
logger.Info("Service started")
|
|
|
|
go s.watch()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) Stop() {
|
|
close(s.stopChan)
|
|
s.watcher.Close()
|
|
signal.Stop(s.sigChan)
|
|
}
|
|
|
|
func (s *Service) watch() {
|
|
for {
|
|
select {
|
|
case event, ok := <-s.watcher.Events:
|
|
if !ok {
|
|
return
|
|
}
|
|
if event.Op&fsnotify.Create == fsnotify.Create {
|
|
go s.handleNewTerminal(event.Name)
|
|
}
|
|
|
|
case err, ok := <-s.watcher.Errors:
|
|
if !ok {
|
|
return
|
|
}
|
|
logger.Error("Watcher error: %v", err)
|
|
|
|
case sig := <-s.sigChan:
|
|
logger.Debug("Received signal: %v", sig)
|
|
return
|
|
|
|
case <-s.stopChan:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// 判断是否为有效的终端设备
|
|
func isValidTerminal(path string) bool {
|
|
// 只处理 tty 和 pts 设备
|
|
basename := filepath.Base(path)
|
|
|
|
// 如果是tty设备,直接返回true
|
|
if strings.HasPrefix(basename, "tty") {
|
|
return true
|
|
}
|
|
|
|
// 检查设备文件是否真实存在且可写
|
|
info, err := os.Stat(path)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
// 检查是否为字符设备
|
|
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
|
|
// 检查是否为字符设备(参考 Linux 设备文件类型)
|
|
return (stat.Mode & syscall.S_IFMT) == syscall.S_IFCHR
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (s *Service) handleNewTerminal(path string) {
|
|
// 首先检查是否为虚拟终端
|
|
if !isValidTerminal(path) {
|
|
logger.Debug("Skipping no valied terminal: %s", path)
|
|
return
|
|
}
|
|
|
|
// 创建一个带超时的context
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
|
|
// 使用channel来控制任务完成
|
|
done := make(chan bool)
|
|
|
|
go func() {
|
|
defer close(done)
|
|
|
|
// 尝试读取欢迎文件内容
|
|
content, err := readWelcomeFile()
|
|
if err != nil {
|
|
logger.Error("Failed to read welcome file: %v", err)
|
|
return
|
|
}
|
|
|
|
// 尝试打开终端
|
|
f, err := os.OpenFile(path, os.O_WRONLY, 0)
|
|
if err != nil {
|
|
logger.Error("Error opening terminal %s: %v", path, err)
|
|
return
|
|
}
|
|
defer f.Close()
|
|
|
|
// 写入文件内容
|
|
if _, err = f.WriteString(content); err != nil {
|
|
logger.Error("Error writing to terminal %s: %v", path, err)
|
|
return
|
|
}
|
|
|
|
// 写入换行符
|
|
if _, err = f.Write([]byte{'\n'}); err != nil {
|
|
logger.Error("Error sending newline to terminal %s: %v", path, err)
|
|
return
|
|
}
|
|
|
|
logger.Info("Welcome message sent to %s", path)
|
|
done <- true
|
|
}()
|
|
|
|
// 等待任务完成或超时
|
|
select {
|
|
case <-ctx.Done():
|
|
logger.Error("Timeout while sending welcome message to %s", path)
|
|
return
|
|
case <-done:
|
|
return
|
|
}
|
|
}
|
|
|
|
// 读取欢迎文件内容的辅助函数
|
|
func readWelcomeFile() (string, error) {
|
|
// 检查文件是否存在
|
|
if _, err := os.Stat(constants.WelcomeFilePath); os.IsNotExist(err) {
|
|
return "已连接到北溟服务,您执行的命令将由北溟提供帮助", nil
|
|
}
|
|
|
|
// 读取文件内容
|
|
content, err := os.ReadFile(constants.WelcomeFilePath)
|
|
if err != nil {
|
|
return "已连接到北溟服务,您执行的命令将由北溟提供帮助", nil
|
|
}
|
|
|
|
// 去除多余的空白字符
|
|
return strings.TrimSpace(string(content)), nil
|
|
}
|