github.com/isyscore/isc-gobase@v1.5.3-0.20231218061332-cbc7451899e9/system/process/process_posix.go (about)

     1  //go:build linux || darwin
     2  
     3  package process
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"github.com/isyscore/isc-gobase/system/common"
     9  	"golang.org/x/sys/unix"
    10  	"os"
    11  	"os/user"
    12  	"path/filepath"
    13  	"strconv"
    14  	"strings"
    15  	"syscall"
    16  )
    17  
    18  // POSIX
    19  func getTerminalMap() (map[uint64]string, error) {
    20  	ret := make(map[uint64]string)
    21  	var termfiles []string
    22  
    23  	d, err := os.Open("/dev")
    24  	if err != nil {
    25  		return nil, err
    26  	}
    27  	defer func(d *os.File) {
    28  		_ = d.Close()
    29  	}(d)
    30  
    31  	devnames, err := d.Readdirnames(-1)
    32  	if err != nil {
    33  		return nil, err
    34  	}
    35  	for _, devname := range devnames {
    36  		if strings.HasPrefix(devname, "/dev/tty") {
    37  			termfiles = append(termfiles, "/dev/tty/"+devname)
    38  		}
    39  	}
    40  
    41  	var ptsnames []string
    42  	ptsd, err := os.Open("/dev/pts")
    43  	if err != nil {
    44  		ptsnames, _ = filepath.Glob("/dev/ttyp*")
    45  		if ptsnames == nil {
    46  			return nil, err
    47  		}
    48  	}
    49  	defer func(ptsd *os.File) {
    50  		_ = ptsd.Close()
    51  	}(ptsd)
    52  
    53  	if ptsnames == nil {
    54  		defer func(ptsd *os.File) {
    55  			_ = ptsd.Close()
    56  		}(ptsd)
    57  		ptsnames, err = ptsd.Readdirnames(-1)
    58  		if err != nil {
    59  			return nil, err
    60  		}
    61  		for _, ptsname := range ptsnames {
    62  			termfiles = append(termfiles, "/dev/pts/"+ptsname)
    63  		}
    64  	} else {
    65  		termfiles = ptsnames
    66  	}
    67  
    68  	for _, name := range termfiles {
    69  		stat := unix.Stat_t{}
    70  		if err = unix.Stat(name, &stat); err != nil {
    71  			return nil, err
    72  		}
    73  		rdev := uint64(stat.Rdev)
    74  		ret[rdev] = strings.Replace(name, "/dev", "", -1)
    75  	}
    76  	return ret, nil
    77  }
    78  
    79  // isMount is a port of python's os.path.ismount()
    80  // https://github.com/python/cpython/blob/08ff4369afca84587b1c82034af4e9f64caddbf2/Lib/posixpath.py#L186-L216
    81  // https://docs.python.org/3/library/os.path.html#os.path.ismount
    82  func isMount(path string) bool {
    83  	// Check symlinkness with os.Lstat; unix.DT_LNK is not portable
    84  	fileInfo, err := os.Lstat(path)
    85  	if err != nil {
    86  		return false
    87  	}
    88  	if fileInfo.Mode()&os.ModeSymlink != 0 {
    89  		return false
    90  	}
    91  	var stat1 unix.Stat_t
    92  	if err := unix.Lstat(path, &stat1); err != nil {
    93  		return false
    94  	}
    95  	parent := filepath.Join(path, "..")
    96  	var stat2 unix.Stat_t
    97  	if err := unix.Lstat(parent, &stat2); err != nil {
    98  		return false
    99  	}
   100  	return stat1.Dev != stat2.Dev || stat1.Ino == stat2.Ino
   101  }
   102  
   103  func PidExistsWithContext(ctx context.Context, pid int32) (bool, error) {
   104  	if pid <= 0 {
   105  		return false, fmt.Errorf("invalid pid %v", pid)
   106  	}
   107  	proc, err := os.FindProcess(int(pid))
   108  	if err != nil {
   109  		return false, err
   110  	}
   111  
   112  	if isMount(common.HostProc()) { // if /<HOST_PROC>/proc exists and is mounted, check if /<HOST_PROC>/proc/<PID> folder exists
   113  		_, err := os.Stat(common.HostProc(strconv.Itoa(int(pid))))
   114  		if os.IsNotExist(err) {
   115  			return false, nil
   116  		}
   117  		return err == nil, err
   118  	}
   119  
   120  	// procfs does not exist or is not mounted, check PID existence by signalling the pid
   121  	err = proc.Signal(syscall.Signal(0))
   122  	if err == nil {
   123  		return true, nil
   124  	}
   125  	if err.Error() == "os: process already finished" {
   126  		return false, nil
   127  	}
   128  	errno, ok := err.(syscall.Errno)
   129  	if !ok {
   130  		return false, err
   131  	}
   132  	switch errno {
   133  	case syscall.ESRCH:
   134  		return false, nil
   135  	case syscall.EPERM:
   136  		return true, nil
   137  	}
   138  
   139  	return false, err
   140  }
   141  
   142  func (p *Process) SendSignalWithContext(ctx context.Context, sig syscall.Signal) error {
   143  	process, err := os.FindProcess(int(p.Pid))
   144  	if err != nil {
   145  		return err
   146  	}
   147  
   148  	err = process.Signal(sig)
   149  	if err != nil {
   150  		return err
   151  	}
   152  
   153  	return nil
   154  }
   155  
   156  func (p *Process) SuspendWithContext(ctx context.Context) error {
   157  	return p.SendSignalWithContext(ctx, unix.SIGSTOP)
   158  }
   159  
   160  func (p *Process) ResumeWithContext(ctx context.Context) error {
   161  	return p.SendSignalWithContext(ctx, unix.SIGCONT)
   162  }
   163  
   164  func (p *Process) TerminateWithContext(ctx context.Context) error {
   165  	return p.SendSignalWithContext(ctx, unix.SIGTERM)
   166  }
   167  
   168  func (p *Process) KillWithContext(ctx context.Context) error {
   169  	return p.SendSignalWithContext(ctx, unix.SIGKILL)
   170  }
   171  
   172  func (p *Process) UsernameWithContext(ctx context.Context) (string, error) {
   173  	uids, err := p.UidsWithContext(ctx)
   174  	if err != nil {
   175  		return "", err
   176  	}
   177  	if len(uids) > 0 {
   178  		u, err := user.LookupId(strconv.Itoa(int(uids[0])))
   179  		if err != nil {
   180  			return "", err
   181  		}
   182  		return u.Username, nil
   183  	}
   184  	return "", nil
   185  }