bash_go_service/welcome/service/service.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
}