diff --git a/files.go b/files.go index c22abb8..ba0b51b 100644 --- a/files.go +++ b/files.go @@ -1,6 +1,18 @@ package staros -import "os" +import ( + "errors" + "os" +) + +var ERR_ALREADY_LOCKED = errors.New("ALREADY LOCKED") +var ERR_TIMEOUT = errors.New("TIME OUT") + +func NewFileLock(filepath string) FileLock { + return FileLock{ + filepath: filepath, + } +} // 检测文件/文件夹是否存在 func Exists(path string) bool { diff --git a/files_test.go b/files_test.go new file mode 100644 index 0000000..5328fe4 --- /dev/null +++ b/files_test.go @@ -0,0 +1,22 @@ +package staros + +import ( + "fmt" + "os" + "testing" + "time" +) + +func Test_FileLock(t *testing.T) { + filename := "./test.file" + lock := NewFileLock(filename) + lock2 := NewFileLock(filename) + fmt.Println("lock1", lock.LockNoBlocking()) + time.Sleep(time.Second) + fmt.Println("lock2", lock2.LockWithTimeout(time.Second*5)) + fmt.Println("unlock1", lock.Unlock()) + time.Sleep(time.Second) + fmt.Println("lock2", lock2.LockNoBlocking()) + fmt.Println("unlock2", lock2.Unlock()) + os.Remove(filename) +} diff --git a/files_unix.go b/files_unix.go index 51a6cdb..8580863 100644 --- a/files_unix.go +++ b/files_unix.go @@ -3,11 +3,17 @@ package staros import ( + "b612.me/stario" "os" "syscall" "time" ) +type FileLock struct { + fd int + filepath string +} + func timespecToTime(ts syscall.Timespec) time.Time { return time.Unix(int64(ts.Sec), int64(ts.Nsec)) } @@ -19,3 +25,55 @@ func GetFileCreationTime(fileinfo os.FileInfo) time.Time { func GetFileAccessTime(fileinfo os.FileInfo) time.Time { return timespecToTime(fileinfo.Sys().(*syscall.Stat_t).Atim) } + +func (f *FileLock) openFileForLock() error { + fd, err := syscall.Open(f.filepath, syscall.O_CREAT|syscall.O_RDONLY, 0600) + if err != nil { + return err + } + f.filepath = f.filepath + f.fd = fd + return nil +} + +func (f *FileLock) Lock() error { + if err := f.openFileForLock(); err != nil { + return err + } + return syscall.Flock(f.fd, syscall.LOCK_EX) +} + +func (f *FileLock) LockNoBlocking() error { + if err := f.openFileForLock(); err != nil { + return err + } + err := syscall.Flock(f.fd, syscall.LOCK_EX|syscall.LOCK_NB) + if err != nil { + syscall.Close(f.fd) + if err == syscall.EWOULDBLOCK { + return ERR_ALREADY_LOCKED + } + } + return err +} + +func (f *FileLock) Unlock() error { + err := syscall.Flock(f.fd, syscall.LOCK_UN) + if err != nil { + return err + } + return syscall.Close(f.fd) +} + +func (f *FileLock) LockWithTimeout(tm time.Duration) error { + return stario.StopUntilTimeout(tm, func(tmout chan struct{}) error { + err := f.Lock() + select { + case <-tmout: + f.Unlock() + return nil + default: + } + return err + }) +} diff --git a/files_windows.go b/files_windows.go index b76b2f5..7c8aba5 100644 --- a/files_windows.go +++ b/files_windows.go @@ -3,11 +3,17 @@ package staros import ( + "b612.me/win32api" "os" "syscall" "time" ) +type FileLock struct { + filepath string + handle win32api.HANDLE +} + func GetFileCreationTime(fileinfo os.FileInfo) time.Time { d := fileinfo.Sys().(*syscall.Win32FileAttributeData) return time.Unix(0, d.CreationTime.Nanoseconds()) @@ -18,10 +24,82 @@ func GetFileAccessTime(fileinfo os.FileInfo) time.Time { return time.Unix(0, d.LastAccessTime.Nanoseconds()) } -func SetFileTimes(file *os.File,info os.FileInfo) { +func SetFileTimes(file *os.File, info os.FileInfo) { } func SetFileTimesbyTime(file *os.File) { -} \ No newline at end of file +} + +func (f *FileLock) openFileForLock() error { + name, err := syscall.UTF16PtrFromString(f.filepath) + if err != nil { + return err + } + handle, err := syscall.CreateFile( + name, + syscall.GENERIC_READ, + syscall.FILE_SHARE_READ, + nil, + syscall.OPEN_ALWAYS, + syscall.FILE_FLAG_OVERLAPPED|0x00000080, + 0) + if err != nil { + return err + } + f.handle = win32api.HANDLE(handle) + return nil +} + +func (f *FileLock) lockForTimeout(timeout time.Duration, lockType win32api.DWORD) error { + var err error + if err = f.openFileForLock(); err != nil { + return err + } + event, err := win32api.CreateEventW(nil, true, false, nil) + if err != nil { + return err + } + myEvent := &syscall.Overlapped{HEvent: syscall.Handle(event)} + defer syscall.CloseHandle(myEvent.HEvent) + _, err = win32api.LockFileEx(f.handle, lockType, 0, 1, 0, myEvent) + if err == nil { + return nil + } + if err != syscall.ERROR_IO_PENDING { + return err + } + millis := uint32(syscall.INFINITE) + if timeout >= 0 { + millis = uint32(timeout.Nanoseconds() / 1000000) + } + s, err := syscall.WaitForSingleObject(myEvent.HEvent, millis) + switch s { + case syscall.WAIT_OBJECT_0: + // success! + return nil + case syscall.WAIT_TIMEOUT: + f.Unlock() + return ERR_TIMEOUT + default: + f.Unlock() + return err + } +} + +func (f *FileLock) Lock() error { + return f.lockForTimeout(0, win32api.LOCKFILE_EXCLUSIVE_LOCK) +} + +func (f *FileLock) LockWithTimeout(tm time.Duration) error { + return f.lockForTimeout(tm, win32api.LOCKFILE_EXCLUSIVE_LOCK) +} + +func (f *FileLock) LockNoBlocking() error { + return f.lockForTimeout(0, win32api.LOCKFILE_EXCLUSIVE_LOCK|win32api.LOCKFILE_FAIL_IMMEDIATELY) +} + +func (f *FileLock) Unlock() error { + return syscall.Close(syscall.Handle(f.handle)) +} diff --git a/network_unix.go b/network_unix.go index 177cbb2..508bf0c 100644 --- a/network_unix.go +++ b/network_unix.go @@ -87,16 +87,28 @@ func NetSpeedsByName(duration time.Duration, name string) (NetSpeed, error) { // NetConnections return all TCP/UDP/UNIX DOMAIN SOCKET Connections // if your uid != 0 ,and analysePid==true ,you should have CAP_SYS_PRTACE and CAP_DAC_OVERRIDE/CAP_DAC_READ_SEARCH Caps -func NetConnections(analysePid bool) ([]NetConn, error) { +func NetConnections(analysePid bool,types string) ([]NetConn, error) { var result []NetConn var inodeMap map[string]int64 var err error - fileList := []string{ - "/proc/net/tcp", - "/proc/net/tcp6", - "/proc/net/udp", - "/proc/net/udp6", - "/proc/net/unix", + var fileList []string + if types=="" || strings.Contains(strings.ToLower(types),"all") { + fileList = []string{ + "/proc/net/tcp", + "/proc/net/tcp6", + "/proc/net/udp", + "/proc/net/udp6", + "/proc/net/unix", + } + } + if strings.Contains(strings.ToLower(types),"tcp") { + fileList =append(fileList,"/proc/net/tcp","/proc/net/tcp6") + } + if strings.Contains(strings.ToLower(types),"udp") { + fileList =append(fileList,"/proc/net/udp","/proc/net/udp6") + } + if strings.Contains(strings.ToLower(types),"unix") { + fileList =append(fileList,"/proc/net/unix") } if analysePid { inodeMap, err = GetInodeMap() @@ -135,6 +147,9 @@ func GetInodeMap() (map[string]int64, error) { if err != nil { continue } + if !strings.Contains(socket, "socket") { + continue + } start := strings.Index(socket, "[") if start < 0 { continue @@ -147,7 +162,7 @@ func GetInodeMap() (map[string]int64, error) { } } } - return nil, err + return res, err } func analyseNetFiles(data []byte, inodeMap map[string]int64, typed string) ([]NetConn, error) { @@ -177,6 +192,60 @@ func analyseNetFiles(data []byte, inodeMap map[string]int64, typed string) ([]Ne } res.RemoteAddr = ip res.RemotePort = port + //connection state + if strings.Contains(typed, "tcp") { + state, err := strconv.ParseInt(strings.TrimSpace(v[3]), 16, 64) + if err != nil { + return result, err + } + res.Status = TCP_STATE[state] + } + txrx_queue := strings.Split(strings.TrimSpace(v[4]), ":") + if len(txrx_queue) != 2 { + return result, errors.New("not a valid net file") + } + tx_queue, err := strconv.ParseInt(txrx_queue[0], 16, 64) + if err != nil { + return result, err + } + res.TX_Queue = tx_queue + rx_queue, err := strconv.ParseInt(txrx_queue[1], 16, 64) + if err != nil { + return result, err + } + res.RX_Queue = rx_queue + timer := strings.Split(strings.TrimSpace(v[5]), ":") + if len(timer) != 2 { + return result, errors.New("not a valid net file") + } + switch timer[0] { + case "00": + res.TimerActive = "NO_TIMER" + case "01": + //重传定时器 + res.TimerActive = "RETRANSMIT" + case "02": + //连接定时器、FIN_WAIT_2定时器或TCP保活定时器 + res.TimerActive = "KEEPALIVE" + case "03": + //TIME_WAIT定时器 + res.TimerActive = "TIME_WAIT" + case "04": + //持续定时器 + res.TimerActive = "ZERO_WINDOW_PROBE" + default: + res.TimerActive = "UNKNOWN" + } + timerJif, err := strconv.ParseInt(timer[1], 16, 64) + if err != nil { + return result, err + } + res.TimerJiffies = timerJif + timerCnt, err := strconv.ParseInt(strings.TrimSpace(v[6]), 16, 64) + if err != nil { + return result, err + } + res.RtoTimer = timerCnt res.Uid, err = strconv.ParseInt(v[7], 10, 64) if err != nil { return result, err @@ -229,7 +298,7 @@ func analyseUnixFiles(data []byte, inodeMap map[string]int64, typed string) ([]N res.Pid = -1 } else { _, ok := pidMap[res.Pid] - if !ok { + if !ok || pidMap[res.Pid] == nil { tmp, err := FindProcessByPid(res.Pid) if err != nil { pidMap[res.Pid] = nil @@ -237,8 +306,10 @@ func analyseUnixFiles(data []byte, inodeMap map[string]int64, typed string) ([]N pidMap[res.Pid] = &tmp } } - res.Uid = int64(pidMap[res.Pid].RUID) - res.Process = pidMap[res.Pid] + if pidMap[res.Pid] != nil { + res.Uid = int64(pidMap[res.Pid].RUID) + res.Process = pidMap[res.Pid] + } } } res.Typed = typed diff --git a/os_unix.go b/os_unix.go index 73e0f9a..e31e453 100644 --- a/os_unix.go +++ b/os_unix.go @@ -3,6 +3,7 @@ package staros import ( + "bytes" "fmt" "io/ioutil" "os/user" @@ -12,6 +13,8 @@ import ( "time" ) +var clockTicks = 100 // default value + // StartTime 开机时间 func StartTime() time.Time { tmp, _ := readAsString("/proc/stat") @@ -63,9 +66,9 @@ func getCPUSample() (idle, total uint64) { if err != nil { fmt.Println("Error: ", i, fields[i], err) } - total += val // tally up all the numbers to get total ticks - if i == 4 { // idle is the 5th field in the cpu line - idle = val + total += val // tally up all the numbers to get total ticks + if i == 4 || i == 5 { // idle is the 5th field in the cpu line + idle += val } } return @@ -73,6 +76,55 @@ func getCPUSample() (idle, total uint64) { } return } +func splitProcStat(content []byte) []string { + nameStart := bytes.IndexByte(content, '(') + nameEnd := bytes.LastIndexByte(content, ')') + restFields := strings.Fields(string(content[nameEnd+2:])) // +2 skip ') ' + name := content[nameStart+1 : nameEnd] + pid := strings.TrimSpace(string(content[:nameStart])) + fields := make([]string, 3, len(restFields)+3) + fields[1] = string(pid) + fields[2] = string(name) + fields = append(fields, restFields...) + return fields +} + +func getCPUSampleByPid(pid int) float64 { + contents, err := ioutil.ReadFile("/proc/" + strconv.Itoa(pid) + "/stat") + if err != nil { + return 0 + } + fields := splitProcStat(contents) + utime, err := strconv.ParseFloat(fields[14], 64) + if err != nil { + return 0 + } + + stime, err := strconv.ParseFloat(fields[15], 64) + if err != nil { + return 0 + } + + // There is no such thing as iotime in stat file. As an approximation, we + // will use delayacct_blkio_ticks (aggregated block I/O delays, as per Linux + // docs). Note: I am assuming at least Linux 2.6.18 + var iotime float64 + if len(fields) > 42 { + iotime, err = strconv.ParseFloat(fields[42], 64) + if err != nil { + iotime = 0 // Ancient linux version, most likely + } + } else { + iotime = 0 // e.g. SmartOS containers + } + return utime/float64(clockTicks) + stime/float64(clockTicks) + iotime/float64(clockTicks) +} +func CpuUsageByPid(pid int, sleep time.Duration) float64 { + total1 := getCPUSampleByPid(pid) + time.Sleep(sleep) + total2 := getCPUSampleByPid(pid) + return (total2 - total1) / sleep.Seconds() * 100 +} // CpuUsage 获取CPU使用量 func CpuUsage(sleep time.Duration) float64 { diff --git a/process.go b/process.go index 53981c9..39b75f7 100644 --- a/process.go +++ b/process.go @@ -9,6 +9,7 @@ import ( "os/exec" "strings" "sync" + "sync/atomic" "syscall" "time" ) @@ -16,12 +17,11 @@ import ( //StarCmd Is Here type StarCmd struct { - CMD *exec.Cmd - outfile io.ReadCloser - infile io.WriteCloser - errfile io.ReadCloser - running bool - runningChan chan int + CMD *exec.Cmd + outfile io.ReadCloser + infile io.WriteCloser + errfile io.ReadCloser + running int32 //Store AlL of the Standed Outputs stdout []byte //Store All of the Standed Errors @@ -42,11 +42,10 @@ type StarCmd struct { func Command(command string, args ...string) (*StarCmd, error) { var err error shell := new(StarCmd) - shell.running = false + shell.running = 0 shell.prewritetime = time.Millisecond * 200 shell.stdoutBuf = bytes.NewBuffer(make([]byte, 0)) shell.stderrBuf = bytes.NewBuffer(make([]byte, 0)) - shell.runningChan = make(chan int, 3) shell.stopctx, shell.stopctxfunc = context.WithCancel(context.Background()) cmd := exec.Command(command, args...) shell.CMD = cmd @@ -69,10 +68,9 @@ func Command(command string, args ...string) (*StarCmd, error) { func CommandContext(ctx context.Context, command string, args ...string) (*StarCmd, error) { var err error shell := new(StarCmd) - shell.running = false + shell.running = 0 shell.stdoutBuf = bytes.NewBuffer(make([]byte, 0)) shell.stderrBuf = bytes.NewBuffer(make([]byte, 0)) - shell.runningChan = make(chan int, 3) shell.prewritetime = time.Millisecond * 200 shell.stopctx, shell.stopctxfunc = context.WithCancel(context.Background()) cmd := exec.CommandContext(ctx, command, args...) @@ -95,7 +93,7 @@ func CommandContext(ctx context.Context, command string, args ...string) (*StarC } func (starcli *StarCmd) queryStdout(ctx context.Context) { - for starcli.running && starcli.CMD != nil { + for starcli.IsRunning() && starcli.CMD != nil { select { case <-ctx.Done(): return @@ -115,7 +113,7 @@ func (starcli *StarCmd) queryStdout(ctx context.Context) { if err == io.EOF { break } else { - if !strings.Contains(err.Error(),"file already closed") { + if !strings.Contains(err.Error(), "file already closed") { starcli.runerr = err } return @@ -125,7 +123,7 @@ func (starcli *StarCmd) queryStdout(ctx context.Context) { } func (starcli *StarCmd) queryStderr(ctx context.Context) { - for starcli.running && starcli.CMD != nil { + for starcli.IsRunning() && starcli.CMD != nil { select { case <-ctx.Done(): return @@ -145,7 +143,7 @@ func (starcli *StarCmd) queryStderr(ctx context.Context) { if err == io.EOF { break } else { - if !strings.Contains(err.Error(),"file already closed") { + if !strings.Contains(err.Error(), "file already closed") { starcli.runerr = err } return @@ -247,20 +245,38 @@ func (starcli *StarCmd) AllStdErr() error { return err } +func (starcli *StarCmd) setRunning(alive bool) { + if alive { + val := atomic.LoadInt32(&starcli.running) + if val == 0 { + atomic.AddInt32(&starcli.running, 1) + } else { + atomic.AddInt32(&starcli.running, 1-val) + } + return + } + val := atomic.LoadInt32(&starcli.running) + if val == 1 { + atomic.AddInt32(&starcli.running, -1) + } else { + atomic.AddInt32(&starcli.running, -val) + } +} func (starcli *StarCmd) Start() error { if err := starcli.CMD.Start(); err != nil { return err } - starcli.running = true + starcli.setRunning(true) go func() { err := starcli.CMD.Wait() if err != nil { starcli.runerr = err } starcli.stopctxfunc() - starcli.running = false - starcli.exitcode = starcli.CMD.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() - starcli.runningChan <- 1 + starcli.setRunning(false) + if starcli.CMD.ProcessState != nil { + starcli.exitcode = starcli.CMD.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() + } }() go starcli.queryStdout(starcli.stopctx) go starcli.queryStderr(starcli.stopctx) @@ -282,11 +298,11 @@ func (starcli *StarCmd) Start() error { } func (starcli *StarCmd) IsRunning() bool { - return starcli.running + return 0 != atomic.LoadInt32(&starcli.running) } -func (starcli *StarCmd) Stoped() <-chan int { - return starcli.runningChan +func (starcli *StarCmd) Stoped() <-chan struct{} { + return starcli.stopctx.Done() } func (starcli *StarCmd) Exec(cmd string, wait int) (string, error) { @@ -313,12 +329,12 @@ func (starcli *StarCmd) ExitCode() int { return starcli.exitcode } -func (starcli *StarCmd) Kill() error{ - err:=starcli.CMD.Process.Kill() - if err!=nil{ +func (starcli *StarCmd) Kill() error { + err := starcli.CMD.Process.Kill() + if err != nil { return err } - starcli.running = false + starcli.setRunning(false) return nil } diff --git a/process_unix.go b/process_unix.go index 3b3e193..6683f39 100644 --- a/process_unix.go +++ b/process_unix.go @@ -36,7 +36,7 @@ func FindProcess(compare func(Process) bool) (datas []Process, err error) { if err != nil { return } - netInfo, netErr = NetConnections(false) + netInfo, netErr = NetConnections(false, "") appendNetInfo := func(p *Process) { if netErr != nil { p.netErr = netErr @@ -170,7 +170,7 @@ func FindProcessByPid(pid int64) (datas Process, err error) { err = errors.New("Not Found") return } - netInfo, netErr := NetConnections(false) + netInfo, netErr := NetConnections(false, "") appendNetInfo := func(p *Process) { if netErr != nil { p.netErr = netErr @@ -283,12 +283,12 @@ func Daemon(path string, args ...string) (int, error) { return pid, err } -func DaemonWithUser(uid, gid uint32,groups []uint32,path string, args ...string) (int, error) { +func DaemonWithUser(uid, gid uint32, groups []uint32, path string, args ...string) (int, error) { cmd := exec.Command(path, args...) cmd.SysProcAttr = &syscall.SysProcAttr{ Credential: &syscall.Credential{ - Uid: uid, - Gid: gid, + Uid: uid, + Gid: gid, Groups: groups, }, Setsid: true, @@ -301,11 +301,11 @@ func DaemonWithUser(uid, gid uint32,groups []uint32,path string, args ...string) return pid, err } -func (starcli *StarCmd) SetRunUser(uid, gid uint32,groups []uint32) { +func (starcli *StarCmd) SetRunUser(uid, gid uint32, groups []uint32) { starcli.CMD.SysProcAttr = &syscall.SysProcAttr{ Credential: &syscall.Credential{ - Uid: uid, - Gid: gid, + Uid: uid, + Gid: gid, Groups: groups, }, Setsid: true, @@ -318,13 +318,16 @@ func (starcli *StarCmd) Release() error { Setsid: true, } } else { - starcli.CMD.SysProcAttr.Setsid = true + if !starcli.CMD.SysProcAttr.Setsid { + starcli.CMD.SysProcAttr.Setsid = true + } } if !starcli.IsRunning() { if err := starcli.CMD.Start(); err != nil { return err } } + time.Sleep(time.Millisecond * 10) return starcli.CMD.Process.Release() } diff --git a/sysconf/sysconf.go b/sysconf/sysconf.go index 2b0cc97..784b5e9 100644 --- a/sysconf/sysconf.go +++ b/sysconf/sysconf.go @@ -78,8 +78,7 @@ func (syscfg *SysConf) ParseFromFile(filepath string) error { if err != nil { return err } - syscfg.Parse(data) - return nil + return syscfg.Parse(data) } // Parse 生成INI文件结构 diff --git a/typed.go b/typed.go index 71a987b..7798734 100644 --- a/typed.go +++ b/typed.go @@ -11,6 +11,22 @@ const ( TB = GB << 10 PB = TB << 10 ) +const ( + TCP_UNKNOWN = iota + TCP_ESTABLISHED + TCP_SYN_SENT + TCP_SYN_RECV + TCP_FIN_WAIT1 + TCP_FIN_WAIT2 + TCP_TIME_WAIT + TCP_CLOSE + TCP_CLOSE_WAIT + TCP_LAST_ACL + TCP_LISTEN + TCP_CLOSING +) + +var TCP_STATE = []string{"TCP_UNKNOWN", "TCP_ESTABLISHED", "TCP_SYN_SENT", "TCP_SYN_RECV", "TCP_FIN_WAIT1", "TCP_FIN_WAIT2", "TCP_TIME_WAIT", "TCP_CLOSE", "TCP_CLOSE_WAIT", "TCP_LAST_ACL", "TCP_LISTEN", "TCP_CLOSING"} type NetAdapter struct { Name string @@ -79,14 +95,20 @@ type DiskStatus struct { } type NetConn struct { - LocalAddr string - LocalPort int - Typed string - RemoteAddr string - RemotePort int - Socket string - Inode string - Pid int64 - Uid int64 - Process *Process + LocalAddr string + LocalPort int + Typed string + RemoteAddr string + RemotePort int + Socket string + Inode string + Status string + TX_Queue int64 + RX_Queue int64 + TimerActive string + TimerJiffies int64 + RtoTimer int64 + Pid int64 + Uid int64 + Process *Process }