| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576 |
- // Copyright 2019 Yunion
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package qga
- import (
- "bytes"
- "encoding/base64"
- "fmt"
- "os"
- "path"
- "regexp"
- "strconv"
- "strings"
- "syscall"
- "time"
- "yunion.io/x/log"
- "yunion.io/x/pkg/errors"
- "yunion.io/x/pkg/utils"
- "yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver"
- )
- var _ fsdriver.IDiskPartition = &QemuGuestAgentPartition{}
- type QemuGuestAgentPartition struct {
- agent *QemuGuestAgent
- }
- func NewQGAPartition(agent *QemuGuestAgent) *QemuGuestAgentPartition {
- return &QemuGuestAgentPartition{
- agent: agent,
- }
- }
- func (qga *QemuGuestAgent) CommandWithTimeout(
- cmdPath string, args, env []string, inputData string, captureOutput bool,
- timeoutSecond int,
- ) (int, string, string, error) {
- pid, err := qga.GuestExecCommand(cmdPath, args, env, inputData, captureOutput)
- if err != nil {
- return -1, "", "", errors.Wrap(err, "GuestExecCommand")
- }
- if timeoutSecond <= 0 {
- timeoutSecond = QGA_EXEC_DEFAULT_WAIT_TIMEOUT
- }
- for i := 0; i < timeoutSecond; i++ {
- execStatus, err := qga.GuestExecStatusCommand(pid.Pid)
- if err != nil {
- return -1, "", "", errors.Wrap(err, "GuestExecStatusCommand")
- }
- if !execStatus.Exited {
- time.Sleep(time.Second)
- } else {
- if captureOutput {
- var stdout, stderr string
- if len(execStatus.OutData) > 0 {
- stdoutb, err := base64.StdEncoding.DecodeString(execStatus.OutData)
- if err != nil {
- return -1, "", "", errors.Wrap(err, "base64.StdEncoding.DecodeString")
- }
- stdout = string(stdoutb)
- }
- if len(execStatus.ErrData) > 0 {
- stderrb, err := base64.StdEncoding.DecodeString(execStatus.ErrData)
- if err != nil {
- return -1, "", "", errors.Wrap(err, "base64.StdEncoding.DecodeString")
- }
- stderr = string(stderrb)
- }
- return execStatus.Exitcode, stdout, stderr, nil
- } else {
- return execStatus.Exitcode, "", "", nil
- }
- }
- }
- return -1, "", "", errors.Errorf("QGA guest-exec wait process exit timeout after wait %d second", timeoutSecond)
- }
- func (qga *QemuGuestAgent) FileGetContents(path string) (string, error) {
- fileno, err := qga.QgaFileOpen(path, "r")
- if err != nil {
- return "", err
- }
- defer func() {
- if e := qga.QgaFileClose(fileno); e != nil {
- log.Errorf("failed close path %s: %s", path, e)
- }
- }()
- var buf bytes.Buffer
- for {
- content, eof, err := qga.QgaFileRead(fileno, -1)
- if err != nil {
- return "", err
- }
- log.Debugf("read %s", content)
- if len(content) > 0 {
- buf.Write(content)
- }
- if eof {
- break
- }
- }
- return buf.String(), nil
- }
- func (qga *QemuGuestAgent) FilePutContents(path, content string, modAppend bool) error {
- var mode = "w+"
- if modAppend {
- mode = "a+"
- }
- fileno, err := qga.QgaFileOpen(path, mode)
- if err != nil {
- return err
- }
- defer func() {
- if e := qga.QgaFileClose(fileno); e != nil {
- log.Errorf("failed close path %s: %s", path, e)
- }
- }()
- var buf = bytes.NewBufferString(content)
- for {
- log.Debugf("write %s", buf.String())
- count, _, err := qga.QgaFileWrite(fileno, buf.String())
- if err != nil {
- return err
- }
- if count > 0 {
- buf.Next(count)
- } else {
- if buf.Len() > 0 {
- return errors.Errorf("qga file put content remaining %d", buf.Len())
- } else {
- break
- }
- }
- }
- return nil
- }
- func (p *QemuGuestAgentPartition) osPathExists(path string) (bool, error) {
- retCode, _, _, err := p.agent.CommandWithTimeout("test", []string{"-e", path}, nil, "", false, -1)
- if err != nil {
- return false, errors.Wrap(err, "CommandWithTimeout")
- }
- if retCode == 0 {
- return true, nil
- }
- retCode, _, _, err = p.agent.CommandWithTimeout("test", []string{"-L", path}, nil, "", false, -1)
- if err != nil {
- return false, errors.Wrap(err, "CommandWithTimeout")
- }
- return retCode == 0, nil
- }
- func (p *QemuGuestAgentPartition) osIsDir(path string) (bool, error) {
- retCode, _, _, err := p.agent.CommandWithTimeout("test", []string{"-d", path}, nil, "", false, -1)
- if err != nil {
- return false, errors.Wrap(err, "CommandWithTimeout")
- }
- return retCode == 0, nil
- }
- func (p *QemuGuestAgentPartition) osListDir(path string) ([]string, error) {
- if ok, err := p.osIsDir(path); err != nil {
- return nil, err
- } else if !ok {
- return nil, errors.Errorf("Path %s is not dir", path)
- }
- retcode, stdout, stderr, err := p.agent.CommandWithTimeout("ls", []string{"-a", path}, nil, "", true, -1)
- if err != nil {
- return nil, errors.Wrapf(err, "command ls -a %s", path)
- }
- if retcode > 0 {
- return nil, errors.Errorf("failed guest-exec ls -a %s: %s %s %d", path, stdout, stderr, retcode)
- }
- files := []string{}
- strfiles := strings.Split(stdout, "\n")
- for _, f := range strfiles {
- f = strings.TrimSpace(f)
- if !utils.IsInStringArray(f, []string{"", ".", ".."}) {
- files = append(files, f)
- }
- }
- return files, nil
- }
- func (p *QemuGuestAgentPartition) GetLocalPath(sPath string, caseInsensitive bool) string {
- if sPath == "." {
- sPath = ""
- }
- if !caseInsensitive {
- return sPath
- }
- var fullPath = "/"
- pathSegs := strings.Split(sPath, "/")
- for _, seg := range pathSegs {
- if len(seg) == 0 {
- continue
- }
- var realSeg string
- files, err := p.osListDir(fullPath)
- if err != nil {
- log.Errorf("List dir %s error: %v", sPath, err)
- return ""
- }
- for _, f := range files {
- if f == seg || (caseInsensitive && (strings.ToLower(f)) == strings.ToLower(seg)) {
- realSeg = f
- break
- }
- }
- if len(realSeg) > 0 {
- fullPath = path.Join(fullPath, realSeg)
- } else {
- return ""
- }
- }
- log.Debugf("QGA GetLocalPath %s=>%s", sPath, fullPath)
- return fullPath
- }
- func (p *QemuGuestAgentPartition) FileGetContents(sPath string, caseInsensitive bool) ([]byte, error) {
- sPath = p.GetLocalPath(sPath, caseInsensitive)
- return p.FileGetContentsByPath(sPath)
- }
- func (p *QemuGuestAgentPartition) FileGetContentsByPath(sPath string) ([]byte, error) {
- res, err := p.agent.FileGetContents(sPath)
- if err != nil {
- return nil, err
- }
- return []byte(res), nil
- }
- func (p *QemuGuestAgentPartition) FilePutContents(sPath, content string, modAppend, caseInsensitive bool) error {
- sFilePath := p.GetLocalPath(sPath, caseInsensitive)
- if len(sFilePath) > 0 {
- sPath = sFilePath
- } else {
- dirPath := p.GetLocalPath(path.Dir(sPath), caseInsensitive)
- if len(dirPath) > 0 {
- sPath = path.Join(dirPath, path.Base(sPath))
- }
- }
- if len(sPath) > 0 {
- return p.agent.FilePutContents(sPath, content, modAppend)
- } else {
- return errors.Errorf("Can't put content to empty Path")
- }
- }
- func (p *QemuGuestAgentPartition) Exists(sPath string, caseInsensitive bool) bool {
- sPath = p.GetLocalPath(sPath, caseInsensitive)
- if len(sPath) > 0 {
- exists, err := p.osPathExists(sPath)
- if err != nil {
- log.Errorf("QGA failed detect path exist %s", err)
- }
- return exists
- }
- return false
- }
- func (p *QemuGuestAgentPartition) Chown(sPath string, uid, gid int, caseInsensitive bool) error {
- sPath = p.GetLocalPath(sPath, caseInsensitive)
- if len(sPath) == 0 {
- return errors.Errorf("Can't get local path: %s", sPath)
- }
- args := []string{fmt.Sprintf("%d.%d", uid, gid), sPath}
- retCode, stdout, stderr, err := p.agent.CommandWithTimeout("chown", args, nil, "", true, -1)
- if err != nil {
- return errors.Wrap(err, "CommandWithTimeout")
- }
- if retCode != 0 {
- return errors.Errorf("QGA chown failed %s %s, retCode %d", stdout, stderr, retCode)
- }
- return nil
- }
- func (p *QemuGuestAgentPartition) Chmod(sPath string, mode uint32, caseInsensitive bool) error {
- sPath = p.GetLocalPath(sPath, caseInsensitive)
- if sPath == "" {
- return nil
- }
- modeStr := fmt.Sprintf("%o", mode&0777)
- args := []string{modeStr, sPath}
- retCode, stdout, stderr, err := p.agent.CommandWithTimeout("chmod", args, nil, "", true, -1)
- if err != nil {
- return errors.Wrap(err, "CommandWithTimeout")
- }
- if retCode != 0 {
- return errors.Errorf("QGA chmod failed %s %s, retCode %d", stdout, stderr, retCode)
- }
- return nil
- }
- func (p *QemuGuestAgentPartition) CheckOrAddUser(user, homeDir string, isSys bool) (string, error) {
- // check user
- retCode, stdout, stderr, err := p.agent.CommandWithTimeout("/bin/cat", []string{"/etc/passwd"}, nil, "", true, -1)
- if err != nil {
- return "", errors.Wrap(err, "CommandWithTimeout")
- }
- if retCode != 0 {
- return "", errors.Errorf("QGA cat passwd failed %s %s, retCode %d", stdout, stderr, retCode)
- }
- var exist = false
- var realHomeDir = ""
- lines := strings.Split(stdout, "\n")
- for i := len(lines) - 1; i >= 0; i-- {
- userInfos := strings.Split(strings.TrimSpace(lines[i]), ":")
- if len(userInfos) < 6 {
- continue
- }
- if userInfos[0] != user {
- continue
- }
- exist = true
- realHomeDir = userInfos[5]
- break
- }
- if exist {
- args := []string{"-R", "/", "-E", "-1", "-m", "0", "-M", "99999", "-I", "-1", user}
- retCode, stdout, stderr, err = p.agent.CommandWithTimeout("chage", args, nil, "", true, -1)
- if err != nil {
- return "", errors.Wrap(err, "CommandWithTimeout")
- }
- if retCode != 0 && !strings.Contains(stderr, "not found") {
- return "", errors.Errorf("failed chage %s %s", stdout, stderr)
- }
- if !p.Exists(realHomeDir, false) {
- err = p.Mkdir(realHomeDir, 0700, false)
- if err != nil {
- return "", errors.Wrapf(err, "Mkdir %s", realHomeDir)
- }
- retCode, stdout, stderr, err = p.agent.CommandWithTimeout("chown", []string{user, realHomeDir}, nil, "", true, -1)
- if err != nil {
- return "", errors.Wrap(err, "CommandWithTimeout")
- }
- if retCode != 0 {
- return "", errors.Errorf("failed chown %s %s: %s %s, retcode %d", user, realHomeDir, stdout, stderr, retCode)
- }
- }
- return realHomeDir, nil
- }
- return path.Join(homeDir, user), p.userAdd(user, homeDir, isSys)
- }
- func (p *QemuGuestAgentPartition) userAdd(user, homeDir string, isSys bool) error {
- args := []string{"-m", "-s", "/bin/bash", user}
- if isSys {
- args = append(args, "-r", "-e", "''", "-f", "'-1'", "-K", "'PASS_MAX_DAYS=-1'")
- }
- if len(homeDir) > 0 {
- args = append(args, "-d", path.Join(homeDir, user))
- }
- retCode, stdout, stderr, err := p.agent.CommandWithTimeout("useradd", args, nil, "", true, -1)
- if err != nil {
- return errors.Wrap(err, "CommandWithTimeout")
- }
- if retCode != 0 {
- return errors.Errorf("failed useradd %s: %s %s, retcode %d", user, stdout, stderr, retCode)
- }
- return nil
- }
- func (p *QemuGuestAgentPartition) Stat(sPath string, caseInsensitive bool) os.FileInfo {
- sPath = p.GetLocalPath(sPath, caseInsensitive)
- if len(sPath) == 0 {
- return nil
- }
- args := []string{"-a", "-l", "-n", "-i", "-s", "-d", sPath}
- retCode, stdout, stderr, err := p.agent.CommandWithTimeout("ls", args, nil, "", true, -1)
- if err != nil {
- log.Errorf("CommandWithTimeout %s", err)
- return nil
- }
- if retCode != 0 {
- log.Errorf("failed ls %s: %s %s, retcode %d", sPath, stdout, stderr, retCode)
- return nil
- }
- ret := strings.Split(stdout, "\n")
- for _, line := range ret {
- dat := regexp.MustCompile(`\s+`).Split(strings.TrimSpace(line), -1)
- if len(dat) > 7 && ((dat[2][0] != 'l' && dat[len(dat)-1] == sPath) ||
- (dat[2][0] == 'l' && dat[len(dat)-3] == sPath)) {
- stMode, err := fsdriver.ModeStr2Bin(dat[2])
- if err != nil {
- log.Errorf("ModeStr2Bin %s", err)
- return nil
- }
- stIno, _ := strconv.Atoi(dat[0])
- stUid, _ := strconv.Atoi(dat[4])
- stGid, _ := strconv.Atoi(dat[5])
- stSize, _ := strconv.Atoi(dat[6])
- info := fsdriver.NewFileInfo(
- sPath,
- int64(stSize),
- os.FileMode(stMode),
- dat[2][0] == 'd',
- &syscall.Stat_t{
- Ino: uint64(stIno),
- Uid: uint32(stUid),
- Gid: uint32(stGid),
- Size: int64(stSize),
- },
- )
- return info
- }
- }
- return nil
- }
- func (p *QemuGuestAgentPartition) Symlink(src, dst string, caseInsensitive bool) error {
- odstDir := path.Dir(dst)
- if err := p.Mkdir(odstDir, 0755, caseInsensitive); err != nil {
- return errors.Wrapf(err, "Mkdir %s", odstDir)
- }
- if p.Exists(dst, caseInsensitive) {
- p.Remove(dst, caseInsensitive)
- }
- odstDir = p.GetLocalPath(odstDir, caseInsensitive)
- dst = path.Join(odstDir, path.Base(dst))
- retCode, stdout, stderr, err := p.agent.CommandWithTimeout("ln", []string{"-s", src, dst}, nil, "", true, -1)
- if err != nil {
- return errors.Wrap(err, "CommandWithTimeout")
- }
- if retCode != 0 {
- return errors.Errorf("failed ln -s %s %s: %s %s, retcode %d", src, dst, stdout, stderr, retCode)
- }
- return nil
- }
- func (p *QemuGuestAgentPartition) Passwd(account, password string, caseInsensitive bool) error {
- return p.agent.GuestSetUserPassword(account, password, false)
- }
- func (p *QemuGuestAgentPartition) Mkdir(sPath string, mode int, caseInsensitive bool) error {
- segs := strings.Split(sPath, "/")
- sp := ""
- pPath := p.GetLocalPath("/", caseInsensitive)
- var err error
- for _, s := range segs {
- if len(s) > 0 {
- sp = path.Join(sp, s)
- vPath := p.GetLocalPath(sp, caseInsensitive)
- if len(vPath) == 0 {
- err = p.osMkdirP(path.Join(pPath, s), uint32(mode))
- pPath = p.GetLocalPath(sp, caseInsensitive)
- } else {
- pPath = vPath
- }
- }
- }
- return err
- }
- func (p *QemuGuestAgentPartition) osMkdirP(dir string, mode uint32) error {
- retCode, stdout, stderr, err := p.agent.CommandWithTimeout("mkdir", []string{"-p", dir}, nil, "", true, -1)
- if err != nil {
- return errors.Wrap(err, "CommandWithTimeout")
- }
- if retCode != 0 {
- return errors.Errorf("mkdir -p %s: %s %s: retCode: %d", dir, stdout, stderr, retCode)
- }
- return p.Chmod(dir, mode, false)
- }
- func (p *QemuGuestAgentPartition) ListDir(sPath string, caseInsensitive bool) []string {
- sPath = p.GetLocalPath(sPath, caseInsensitive)
- if len(sPath) > 0 {
- ret, err := p.osListDir(sPath)
- if err != nil {
- log.Errorf("list dir for %s: %v", sPath, err)
- return nil
- }
- return ret
- }
- return nil
- }
- func (p *QemuGuestAgentPartition) Remove(sPath string, caseInsensitive bool) {
- sPath = p.GetLocalPath(sPath, caseInsensitive)
- if len(sPath) > 0 {
- retCode, stdout, stderr, err := p.agent.CommandWithTimeout("rm", []string{sPath}, nil, "", true, -1)
- if err != nil {
- log.Errorf("remove %s: %s", sPath, err)
- return
- }
- if retCode != 0 {
- log.Errorf("remove %s: %s %s: retCode: %d", sPath, stdout, stderr, retCode)
- return
- }
- }
- }
- func (p *QemuGuestAgentPartition) Cleandir(dir string, keepdir, caseInsensitive bool) error {
- sPath := p.GetLocalPath(dir, caseInsensitive)
- if len(sPath) > 0 {
- retCode, stdout, stderr, err := p.agent.CommandWithTimeout("rm", []string{"-rf", sPath}, nil, "", true, -1)
- if err != nil {
- return errors.Wrapf(err, "remove -rf %s", sPath)
- }
- if retCode != 0 {
- return errors.Wrapf(err, "remove -rf %s: %s %s: retCode: %d", sPath, stdout, stderr, retCode)
- }
- }
- return nil
- }
- func (*QemuGuestAgentPartition) Zerofiles(dir string, caseInsensitive bool) error {
- return nil
- }
- func (*QemuGuestAgentPartition) SupportSerialPorts() bool {
- return false
- }
- func (*QemuGuestAgentPartition) GetPartDev() string {
- return "QGA"
- }
- func (*QemuGuestAgentPartition) IsMounted() bool {
- return true
- }
- func (*QemuGuestAgentPartition) Mount() bool {
- return true
- }
- func (*QemuGuestAgentPartition) MountPartReadOnly() bool {
- return false
- }
- func (*QemuGuestAgentPartition) Umount() error {
- return nil
- }
- func (*QemuGuestAgentPartition) GetMountPath() string {
- return ""
- }
- func (*QemuGuestAgentPartition) IsReadonly() bool {
- return false
- }
- func (*QemuGuestAgentPartition) GetPhysicalPartitionType() string {
- return ""
- }
- func (*QemuGuestAgentPartition) Zerofree() {
- }
- func (*QemuGuestAgentPartition) GenerateSshHostKeys() error {
- return nil
- }
|