package utils import ( "archive/tar" "archive/zip" "bufio" "bytes" "compress/gzip" "crypto/md5" "encoding/base64" "encoding/json" "errors" "fmt" "github.com/coroot/coroot-node-agent/utils/enums" "io" "io/ioutil" "net/http" "os" "os/exec" "path/filepath" "regexp" "runtime" "runtime/debug" "strconv" "strings" . "strings" "sync" "time" log "github.com/sirupsen/logrus" ) var ( agentdaemon_home = "" AgentsPath = "agents" BinPath = "bin" ConfPath = "conf" LogsPath = "logs" LibsPath = "libs" ConfName = "daemon.conf" ComIniFile = "common.ini" LogName = "daemon.log" ScriptPath = "scripts" RuntimePath = "runtime" MetaPath = "meta" ) func init() { // TODO: 后面校验所有的路径,都要以agentdaemon_home开头.... @jay // 初始化时获取omniagent的工作目录 if ex, err := os.Executable(); err != nil { agentdaemon_home = "" // TODO: 不应该exit吗?@jay } else { agentdaemon_home = ex } tmp_dir, _ := filepath.EvalSymlinks(os.TempDir()) if tmp_dir != "" && strings.Contains(agentdaemon_home, tmp_dir) { _, filename, _, ok := runtime.Caller(0) if ok { agentdaemon_home = filepath.Dir(filename) } } agentdaemon_home = filepath.Dir(filepath.Dir(agentdaemon_home)) //log.Infof("omniagent work path %s", agentdaemon_home) _, filename := filepath.Split(agentdaemon_home) if !strings.EqualFold(filename, "omniagent") && !strings.EqualFold(filename, "doccagent") && !strings.EqualFold(filename, "cloudwise") { // 考虑后续支持自定义目录安装的情况 卸载逻辑在uninstall.sh中判断 //panic(errors.New(fmt.Sprintf("get exec path[%s] is not doccagent work path panic error", agentdaemon_home))) } //log.Infof("omniagent exec path %s", agentdaemon_home) //initCntrPidConvert() } func MD5(src string) (dest string) { dest = fmt.Sprintf("%x", md5.Sum([]byte(src))) return dest } // 检查url是否正确 func CheckUrl(url string) error { if url == "" { err := errors.New("url is empty") return err } // 对接软负载后直接返回uri即可 //if !strings.Contains(url, "http") { // err := errors.New("URL is incorrect,url is " + url) // return err //} return nil } // DecompressTarGzip 将 TAR.GZ 格式的文件解包到指定目录并返回指定文件所在的解包后的目录, // 如果存在多个同名指定文件,则返回第一个找到的目录 func decompress(srcPath, dstPath string) (targetDir string, _ error) { linkMap := make(map[string]string) if err := os.MkdirAll(dstPath, os.ModePerm); err != nil { return "", err } if strings.HasSuffix(srcPath, ".zip") { zr, err := zip.OpenReader(srcPath) if err != nil { return "", err } defer zr.Close() for _, k := range zr.File { info := k.FileInfo() fpath := filepath.Join(dstPath, k.Name) if k.FileInfo().IsDir() { err := os.MkdirAll(fpath, info.Mode()) if err != nil { return "", err } continue } w, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, info.Mode()) if err != nil { return "", err } r, err := k.Open() if err != nil { return "", err } io.Copy(w, r) w.Close() } return dstPath, nil } srcFile, err := os.Open(srcPath) if err != nil { return "", err } defer srcFile.Close() gr, err := gzip.NewReader(srcFile) if err != nil { return "", err } defer gr.Close() tr := tar.NewReader(gr) count := 1 for { header, err := tr.Next() if err == io.EOF { break } else if err != nil { return "", err } if strings.HasPrefix(header.Name, ".") { continue } if header.Linkname != "" { hnd := filepath.Dir(header.Name) hld := filepath.Join(hnd, header.Linkname) linkMap[filepath.Join(dstPath, hld)] = filepath.Join(dstPath, header.Name) continue } fpath := filepath.Join(dstPath, header.Name) if count == 1 { fmt.Println(fpath, header.Name) targetDir = fpath } count++ info := header.FileInfo() if info.IsDir() { if err = os.MkdirAll(fpath, info.Mode()); err != nil { return "", err } continue } if err = extractTarFileToPath(tr, info, fpath); err != nil { return "", err } } linkMapHandle(linkMap) linkMap = nil return targetDir, nil } func decompressWithTar(srcPath, dstPath string) (targetDir string, _ error) { if err := os.MkdirAll(dstPath, os.ModePerm); err != nil { return "", err } if strings.HasSuffix(srcPath, ".zip") { return "", errors.New("unsupported zip file") } compressPath, err := makeCompressFolderAndMvTarFile2(srcPath) if err != nil { return "", err } folderName, err := decompressCjeInCurrentDirectory(compressPath) if err != nil { os.Rename(compressPath, srcPath) return "", err } return filepath.Join(filepath.Dir(srcPath), "compress", folderName), nil } func makeCompressFolderAndMvTarFile2(path string) (string, error) { baseName := filepath.Base(path) folder := filepath.Dir(path) compressPath := filepath.Join(folder, "compress") if _, err := os.Stat(compressPath); os.IsNotExist(err) { os.Mkdir(compressPath, os.ModePerm) } moveToPath := filepath.Join(compressPath, baseName) if err := os.Rename(path, moveToPath); err != nil { return "", err } return moveToPath, nil } func decompressCjeInCurrentDirectory(gzipPath string) (string, error) { folder := filepath.Dir(gzipPath) dir, err := ioutil.ReadDir(folder) for _, d := range dir { if d.Name() == filepath.Base(gzipPath) { continue } else { err = RemovePath(filepath.Join(folder, d.Name()), "") if err != nil { log.Errorf("remove dir error %s", err) } } } cmd := exec.Command("tar", "-vzxf", gzipPath) cmd.Dir = folder err = cmd.Run() if err != nil { return "", err } fileInfoList, err := ioutil.ReadDir(folder) if err != nil { return "", err } for _, file := range fileInfoList { if file.Name() == filepath.Base(gzipPath) { continue } return file.Name(), nil } return "", fmt.Errorf("uncompress faild unknow error") } // extractTarFileToPath 写出 *tar.Reader 对象的内容到指定路径 func extractTarFileToPath(tr *tar.Reader, info os.FileInfo, path string) error { w, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, info.Mode()) if err != nil { return err } defer w.Close() _, err = io.Copy(w, tr) if err != nil { return err } return nil } func linkMapHandle(m map[string]string) { for s, s2 := range m { err := os.Link(s, s2) if err != nil { log.Errorf("linkMapHandle err %s => %s, err is %v", s, s2, err) } } } var CPUNum = float64(runtime.NumCPU()) func FormatPath(s string) string { switch runtime.GOOS { case "windows": return Replace(s, "/", "\\", -1) case "darwin", "linux": return Replace(s, "\\", "/", -1) default: log.Infof("only support linux,windows,darwin, but os is " + runtime.GOOS) return s } } func CopyDir(src string, dest string) { src = FormatPath(src) dest = FormatPath(dest) var cmd *exec.Cmd switch runtime.GOOS { case "windows": cmd = exec.Command("xcopy", src, dest, "/I", "/E") case "darwin", "linux": cmd = exec.Command("cp", "-R", src, dest) } outPut, err := cmd.Output() if err != nil { log.Infof("copyDir %s => %s ,err is => %v", src, dest, err) return } log.Infof("copyDir %s => %s output is => %s", src, dest, string(outPut)) } func WriteConf(path string, new map[string]interface{}) error { info, err := os.Stat(path) if err != nil { log.Errorf("write config[%s] get file info error:%s", path, err) return err } file, err := os.OpenFile(path, os.O_WRONLY, info.Mode()) if err != nil { log.Errorf("write config[%s] open file error:%s", path, err) return err } file.Truncate(0) defer file.Close() data, err := json.MarshalIndent(&new, "", "\t") if err != nil { log.Errorf("write config[%s] write json data error:%s", path, err) return err } file.Write(data) return nil } func HTTPConnect(method, url, token string, data []byte) ([]byte, error) { req, err := http.NewRequest(method, url, bytes.NewBuffer(data)) if err != nil { log.Errorf("create request error:%s.", err) return nil, err } req.Header.Set("Content-Type", "application/json") req.Header.Add("token", token) client := &http.Client{ Timeout: 60 * time.Second, } resp, err := client.Do(req) if err != nil { log.Error(err) return nil, err } defer resp.Body.Close() result, err := ioutil.ReadAll(resp.Body) if err != nil { log.Errorf("http read body error:%s", err) return nil, err } return result, nil } func ReadConfig(data []byte) (map[string]interface{}, error) { result := map[string]interface{}{} err := json.Unmarshal(data, &result) if err != nil { return result, err } return result, nil } func updateConfig(new map[string]interface{}, old map[string]interface{}) map[string]interface{} { for k, v := range new { if value, ok := old[k]; ok { newconf, newok := v.(map[string]interface{}) oldconf, oldok := value.(map[string]interface{}) if newok && oldok { new[k] = updateConfig(newconf, oldconf) continue } new[k] = value } } return new } func UpdateConfig(oldPath, newPath string) error { oldConfByte, err := ioutil.ReadFile(oldPath) if err != nil { return err } oldConf, err := ReadConfig(oldConfByte) if err != nil { return err } newConfByte, err := ioutil.ReadFile(newPath) if err != nil { return err } newConf, err := ReadConfig(newConfByte) if err != nil { return err } new := updateConfig(newConf, oldConf) err = WriteConf(oldPath, new) if err != nil { WriteConf(oldPath, oldConf) return err } return nil } func CopyFile(srcName, dstName string) (written int64, err error) { src, err := os.Open(srcName) if err != nil { return } defer src.Close() dst, err := os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE, 0777) if err != nil { return } defer dst.Close() return io.Copy(dst, src) } func CheckPath(path string) (err error) { path, err = filepath.Abs(path) if err != nil { log.Errorf("check path[%s] get abs path error %s", path, err) return err } // 通过比较路径的长短来判断是否为子路径 if len(path) < len(agentdaemon_home) { log.Errorf("remove path[%s] check path length error", path) return errors.New(fmt.Sprintf("remove path[%s] check path length error", path)) } // 通过比较父路径是否相同来判断是否为子路径 if agentdaemon_home != path[:len(agentdaemon_home)] { log.Errorf("remove path[%s] check path is not subdirectory[%s]", path, agentdaemon_home) return errors.New(fmt.Sprintf("remove path[%s] check path is not subdirectory[%s]", path, agentdaemon_home)) } return nil } // 校验删除的目录是否为doccagent工作目录的子目录 func RemovePath(path, filename string) (err error) { // 校验删除的文件名 _, real_filename := filepath.Split(path) if filename != "" && real_filename != filename { log.Errorf("remove path[%s] check filename is not %s", path, filename) return errors.New(fmt.Sprintf("remove path[%s] check filename is not %s", path, filename)) } stat, err := os.Stat(path) if os.IsNotExist(err) { return nil } if err != nil { log.Errorf("remove path[%s] get file stat error %s", path, err) return err } if stat.IsDir() { err = os.RemoveAll(path) } else { err = os.Remove(path) } if err != nil { log.Errorf("remove path[%s] error %s", path, err) } return err } func GetRootPath() string { return agentdaemon_home } func GetDefaultPath(path_name ...string) string { return filepath.Join(agentdaemon_home, filepath.Join(path_name...)) } func GetDefaultConfigPath() string { return GetDefaultPath(ConfPath, ConfName) } func GetDefaultLogPath() string { return GetDefaultPath(LogsPath) } func GetDefaultLibsPath(path_name ...string) string { return GetDefaultPath(LibsPath, runtime.GOARCH, filepath.Join(path_name...)) } func GetDefaultAgentsPath(path_name ...string) string { return GetDefaultPath(AgentsPath, filepath.Join(path_name...)) } func GetDefaultBasePath() string { return filepath.Join(GetDefaultPath(ScriptPath), "base") } func GetControlProc() string { return filepath.Join(GetDefaultPath(ScriptPath), enums.DaemonCtlProc) } func GetCommonIni() string { return GetDefaultPath(ConfPath, ComIniFile) } func GetPluginInstallParamsPath(pluginId string) string { if pluginId != "" { pluginId += ".conf" } return GetDefaultPath(ConfPath, "agents-installation", pluginId) } func CatchFn(err interface{}) { log.Errorf(fmt.Sprintf("panic : %s\n", err)) log.Errorf(fmt.Sprint(string(debug.Stack()))) } func FileExist(path string) bool { _, err := os.Stat(path) //os.Stat获取文件信息 if os.IsNotExist(err) { return false } return true } /** * 从token中解析accountid/userid */ func DecodeAccountUser(token string) (string, string) { var account_id string var user_id string account_and_user_byte, err := base64.StdEncoding.DecodeString(token) if err != nil { return account_id, user_id } account_and_user_string := strings.Split(string(account_and_user_byte), "@") if len(account_and_user_string) > 0 { account_id = account_and_user_string[0] } return account_id, user_id } /** * 读取原配置项 */ func GetOriginConfig() (map[string]interface{}, error) { configMap := make(map[string]interface{}) content, err := ioutil.ReadFile(GetDefaultConfigPath()) if err != nil { return nil, err } err = json.Unmarshal(content, &configMap) return configMap, err } func Capitalize(str string) string { if len(str) == 0 { return str } upperStr := strings.ToUpper(string(str[0])) if len(str) > 1 { return upperStr + str[1:] } return upperStr } func CreatePidFile() error { pid := os.Getpid() pidFile := GetDefaultPath(RuntimePath, enums.DaemonProc+".pid") // 如果pid文件存在,覆盖写入 if !FileExist(pidFile) { _, err := os.Create(pidFile) if err != nil { return err } } err := ioutil.WriteFile(pidFile, []byte(strconv.Itoa(pid)), 0644) return err } // back up file func BackUpFile(src string, dst string, list []string) error { // backup file backupDir := filepath.Join(dst, "backup") for _, v := range list { srcPath := filepath.Join(src, v) dstPath := filepath.Join(backupDir, v) // rename file err := os.Rename(srcPath, dstPath) if err != nil { // log.Errorf("backup file %s err : %v", v, err) return err } } return nil } func CopyFiles(srcDir string, dstDir string, list []string) error { // if srcDir not exist ,return if _, err := os.Stat(srcDir); os.IsNotExist(err) { log.Debugf("srcDir is not exist %s err : %v", srcDir, err) return err } // if dstdir not exist ,create it if _, err := os.Stat(dstDir); os.IsNotExist(err) { err := os.MkdirAll(dstDir, os.ModePerm) if err != nil { log.Errorf("mkdir dst dir %s err : %v", dstDir, err) return err } } // Copy list dir or file to dstDir for _, v := range list { srcPath := filepath.Join(srcDir, v) dstPath := filepath.Join(dstDir, v) // src is dir fsInfo, err := os.Stat(srcPath) if err != nil { log.Debugf("srcPath is %s, err is %v", srcPath, err) continue } // log err log.Debugf("srcPath is %s, err is %v", srcPath, err) if fsInfo.IsDir() { // mkdir dst dir err := os.MkdirAll(filepath.Dir(dstPath), os.ModePerm) if err != nil { log.Errorf("mkdir dst dir %s err : %v", dstPath, err) return err } CopyDir(srcPath, dstPath) } else { // copy file err = os.MkdirAll(filepath.Dir(dstPath), os.ModePerm) if err != nil { log.Errorf("mkdir dst dir %s err : %v", dstPath, err) return err } _, err := CopyFile(dstPath, srcPath) if err != nil { log.Errorf("copy file %s err : %v", v, err) return err } } } return nil } // // CopyDir 拷贝整个目录 // func CopyDir(src string, dst string, overwrite bool) error { // // 创建目标目录 // err := os.MkdirAll(dst, 0755) // if err != nil { // return err // } // // 打开源目录 // entries, err := os.ReadDir(src) // if err != nil { // return err // } // // 遍历源目录中的条目 // for _, entry := range entries { // sourcePath := filepath.Join(src, entry.Name()) // destinationPath := filepath.Join(dst, entry.Name()) // // 如果是目录,则递归拷贝 // if entry.IsDir() { // err = CopyDir(sourcePath, destinationPath, overwrite) // if err != nil { // return err // } // } else { // // 如果是文件,则拷贝文件 // err = CopyFile(sourcePath, destinationPath, overwrite) // if err != nil { // return err // } // } // } // return nil // } func NewInstallPathParams(installPath string) []string { // check dir is prefix of "/D=" if !strings.HasPrefix(installPath, "/D=") { installPath = "/D=" + installPath } paths := strings.Split(installPath, " ") return paths } func GetHostPid(pid int) (int, error) { if os.Getenv("CW_CONTAINER") == "true" { if cntrPidConvert != nil { hostPid, err := cntrPidConvert.getHostPidByHostProc(pid) if err != nil { log.Errorf("getHostPidByHostProc %d error:%v", pid, err) } else { log.Infof("getHostPidByHostProc %d hostPid:%d", pid, hostPid) } return hostPid, err } hostPid, err := getHostPidBySched(pid) if err != nil { log.Errorf("getHostPidBySched %d error:%v", pid, err) } else { log.Infof("getHostPidBySched %d hostPid:%d", pid, hostPid) } return hostPid, err } else { return pid, nil } } func getHostPidBySched(pid int) (int, error) { hostPid := 0 schedFile := fmt.Sprintf("/proc/%d/sched", pid) file, err := os.Open(schedFile) if err != nil { return 0, err } defer file.Close() scanner := bufio.NewScanner(file) if scanner.Scan() { line := scanner.Text() re := regexp.MustCompile(`\d+`) match := re.FindString(line) if match != "" { hostPid, err = strconv.Atoi(match) if err != nil { return 0, err } } } return hostPid, nil } type cntrPidConvertMgr struct { cntrPidToHostPid map[int]int mtxCntrPidConvert *sync.RWMutex daemonid []byte } var cntrPidConvert *cntrPidConvertMgr func initCntrPidConvert() { if os.Getenv("CW_CONTAINER") == "true" && os.Getenv("CW_HOST_PID_ENABLE") != "true" { if hostPid, err := getHostPidBySched(1); err == nil && hostPid > 1 { log.Infof("not need convert cntrPid to hostPid") return } host_root := os.Getenv("HOST_ROOT") host_proc := os.Getenv("HOST_PROC") log.Infof("host_root %s, host_proc %s", host_root, host_proc) if len(host_proc) <= len(host_root) || host_proc[:len(host_root)] != host_root { log.Errorf("HOST_ROOT %s is not prefix of HOST_PROC %s", host_root, host_proc) return } //若host_proc目录不存在,直接返回 if _, err := os.Stat(host_proc); os.IsNotExist(err) { log.Errorf("host_proc %s not exist", host_proc) return } daemonidPath := filepath.Join(GetDefaultPath(MetaPath, ".daemonid")) daemonid, err := ioutil.ReadFile(daemonidPath) if len(daemonid) == 0 { log.Errorf("read daemonid from %s fail: %v", daemonidPath, err) return } log.Infof("daemonid %s", daemonid) cntrPidConvert = &cntrPidConvertMgr{ cntrPidToHostPid: make(map[int]int), mtxCntrPidConvert: &sync.RWMutex{}, daemonid: daemonid, } } } func (c *cntrPidConvertMgr) getHostPidByHostProc(pid int) (int, error) { c.mtxCntrPidConvert.RLock() hostPid, ok := c.cntrPidToHostPid[pid] c.mtxCntrPidConvert.RUnlock() if ok { return hostPid, nil } var err error c.mtxCntrPidConvert.Lock() defer c.mtxCntrPidConvert.Unlock() hostPid, ok = c.cntrPidToHostPid[pid] if ok { return hostPid, nil } timeStart := time.Now() c.cntrPidToHostPid, err = parseProcDirectory(os.Getenv("OS_HOST_PROC"), c.daemonid) log.Infof("cntrPidToHostPid %#v, time_%s: %#v", c.cntrPidToHostPid, time.Since(timeStart).String(), err) if err != nil { return 0, err } hostPid, ok = c.cntrPidToHostPid[pid] if ok { return hostPid, nil } else { return 0, fmt.Errorf("can not find pid %d in container", pid) } } func parseProcDirectory(dirPath string, daemonid []byte) (pidMap map[int]int, err error) { pidMap = make(map[int]int) daemonidPath := filepath.Join(GetDefaultPath(MetaPath, ".daemonid")) fileInfo, err := os.Stat(daemonidPath) if err != nil { return } daemonidFileSize := fileInfo.Size() // 遍历指定目录下的PID目录 files, err := ioutil.ReadDir(dirPath) if err != nil { return } for _, file := range files { if file.IsDir() { pid := file.Name() pidInt, err := strconv.Atoi(pid) if err != nil { continue } daemonidFilePath := filepath.Join(dirPath, pid, "root/opt/cloudwise/omniagent/meta/.daemonid") // 检查是否存在指定文件并size等于指定大小 fileInfo, err = os.Stat(daemonidFilePath) if err != nil { continue } fileSize := fileInfo.Size() if fileSize != daemonidFileSize { log.Errorf("%s file size %d not equal %d", daemonidFilePath, fileSize, daemonidFileSize) continue } // 检查是否存在指定文件并内容等于指定内容 daemonidContentBytes, err := ioutil.ReadFile(daemonidFilePath) if err == nil && bytes.Compare(daemonidContentBytes, daemonid) == 0 { statusFilePath := filepath.Join(dirPath, pid, "status") // 解析status文件获取NSpid的第二个字符串 nspidValue, err := getNSpidValue(statusFilePath) if err == nil { pidMap[nspidValue] = pidInt } else { log.Errorf("getNSpidValue %s error:%v", statusFilePath, err) } } } } return pidMap, nil } func getNSpidValue(filePath string) (int, error) { content, err := ioutil.ReadFile(filePath) if err != nil { return 0, err } lines := strings.Split(string(content), "\n") for _, line := range lines { if strings.HasPrefix(line, "NSpid:") { fields := strings.Fields(line) if len(fields) >= 3 { valueStr := fields[2] value, err := strconv.Atoi(valueStr) if err != nil { return 0, fmt.Errorf("failed to convert NSpid value %s to integer: %v", valueStr, err) } return value, nil } else if len(fields) == 2 { valueStr := fields[1] value, err := strconv.Atoi(valueStr) if err != nil { return 0, fmt.Errorf("failed to convert NSpid value %s to integer: %v", valueStr, err) } return value, nil } else { log.Errorf("invalid NSpid line format in %s: %s", filePath, line) } } } return 0, errors.New("NSpid value not found!") } func ToString(v interface{}) string { b, err := json.Marshal(v) if err != nil { return err.Error() } return string(b) }