part.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package qga
  15. import (
  16. "bytes"
  17. "encoding/base64"
  18. "fmt"
  19. "os"
  20. "path"
  21. "regexp"
  22. "strconv"
  23. "strings"
  24. "syscall"
  25. "time"
  26. "yunion.io/x/log"
  27. "yunion.io/x/pkg/errors"
  28. "yunion.io/x/pkg/utils"
  29. "yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver"
  30. )
  31. var _ fsdriver.IDiskPartition = &QemuGuestAgentPartition{}
  32. type QemuGuestAgentPartition struct {
  33. agent *QemuGuestAgent
  34. }
  35. func NewQGAPartition(agent *QemuGuestAgent) *QemuGuestAgentPartition {
  36. return &QemuGuestAgentPartition{
  37. agent: agent,
  38. }
  39. }
  40. func (qga *QemuGuestAgent) CommandWithTimeout(
  41. cmdPath string, args, env []string, inputData string, captureOutput bool,
  42. timeoutSecond int,
  43. ) (int, string, string, error) {
  44. pid, err := qga.GuestExecCommand(cmdPath, args, env, inputData, captureOutput)
  45. if err != nil {
  46. return -1, "", "", errors.Wrap(err, "GuestExecCommand")
  47. }
  48. if timeoutSecond <= 0 {
  49. timeoutSecond = QGA_EXEC_DEFAULT_WAIT_TIMEOUT
  50. }
  51. for i := 0; i < timeoutSecond; i++ {
  52. execStatus, err := qga.GuestExecStatusCommand(pid.Pid)
  53. if err != nil {
  54. return -1, "", "", errors.Wrap(err, "GuestExecStatusCommand")
  55. }
  56. if !execStatus.Exited {
  57. time.Sleep(time.Second)
  58. } else {
  59. if captureOutput {
  60. var stdout, stderr string
  61. if len(execStatus.OutData) > 0 {
  62. stdoutb, err := base64.StdEncoding.DecodeString(execStatus.OutData)
  63. if err != nil {
  64. return -1, "", "", errors.Wrap(err, "base64.StdEncoding.DecodeString")
  65. }
  66. stdout = string(stdoutb)
  67. }
  68. if len(execStatus.ErrData) > 0 {
  69. stderrb, err := base64.StdEncoding.DecodeString(execStatus.ErrData)
  70. if err != nil {
  71. return -1, "", "", errors.Wrap(err, "base64.StdEncoding.DecodeString")
  72. }
  73. stderr = string(stderrb)
  74. }
  75. return execStatus.Exitcode, stdout, stderr, nil
  76. } else {
  77. return execStatus.Exitcode, "", "", nil
  78. }
  79. }
  80. }
  81. return -1, "", "", errors.Errorf("QGA guest-exec wait process exit timeout after wait %d second", timeoutSecond)
  82. }
  83. func (qga *QemuGuestAgent) FileGetContents(path string) (string, error) {
  84. fileno, err := qga.QgaFileOpen(path, "r")
  85. if err != nil {
  86. return "", err
  87. }
  88. defer func() {
  89. if e := qga.QgaFileClose(fileno); e != nil {
  90. log.Errorf("failed close path %s: %s", path, e)
  91. }
  92. }()
  93. var buf bytes.Buffer
  94. for {
  95. content, eof, err := qga.QgaFileRead(fileno, -1)
  96. if err != nil {
  97. return "", err
  98. }
  99. log.Debugf("read %s", content)
  100. if len(content) > 0 {
  101. buf.Write(content)
  102. }
  103. if eof {
  104. break
  105. }
  106. }
  107. return buf.String(), nil
  108. }
  109. func (qga *QemuGuestAgent) FilePutContents(path, content string, modAppend bool) error {
  110. var mode = "w+"
  111. if modAppend {
  112. mode = "a+"
  113. }
  114. fileno, err := qga.QgaFileOpen(path, mode)
  115. if err != nil {
  116. return err
  117. }
  118. defer func() {
  119. if e := qga.QgaFileClose(fileno); e != nil {
  120. log.Errorf("failed close path %s: %s", path, e)
  121. }
  122. }()
  123. var buf = bytes.NewBufferString(content)
  124. for {
  125. log.Debugf("write %s", buf.String())
  126. count, _, err := qga.QgaFileWrite(fileno, buf.String())
  127. if err != nil {
  128. return err
  129. }
  130. if count > 0 {
  131. buf.Next(count)
  132. } else {
  133. if buf.Len() > 0 {
  134. return errors.Errorf("qga file put content remaining %d", buf.Len())
  135. } else {
  136. break
  137. }
  138. }
  139. }
  140. return nil
  141. }
  142. func (p *QemuGuestAgentPartition) osPathExists(path string) (bool, error) {
  143. retCode, _, _, err := p.agent.CommandWithTimeout("test", []string{"-e", path}, nil, "", false, -1)
  144. if err != nil {
  145. return false, errors.Wrap(err, "CommandWithTimeout")
  146. }
  147. if retCode == 0 {
  148. return true, nil
  149. }
  150. retCode, _, _, err = p.agent.CommandWithTimeout("test", []string{"-L", path}, nil, "", false, -1)
  151. if err != nil {
  152. return false, errors.Wrap(err, "CommandWithTimeout")
  153. }
  154. return retCode == 0, nil
  155. }
  156. func (p *QemuGuestAgentPartition) osIsDir(path string) (bool, error) {
  157. retCode, _, _, err := p.agent.CommandWithTimeout("test", []string{"-d", path}, nil, "", false, -1)
  158. if err != nil {
  159. return false, errors.Wrap(err, "CommandWithTimeout")
  160. }
  161. return retCode == 0, nil
  162. }
  163. func (p *QemuGuestAgentPartition) osListDir(path string) ([]string, error) {
  164. if ok, err := p.osIsDir(path); err != nil {
  165. return nil, err
  166. } else if !ok {
  167. return nil, errors.Errorf("Path %s is not dir", path)
  168. }
  169. retcode, stdout, stderr, err := p.agent.CommandWithTimeout("ls", []string{"-a", path}, nil, "", true, -1)
  170. if err != nil {
  171. return nil, errors.Wrapf(err, "command ls -a %s", path)
  172. }
  173. if retcode > 0 {
  174. return nil, errors.Errorf("failed guest-exec ls -a %s: %s %s %d", path, stdout, stderr, retcode)
  175. }
  176. files := []string{}
  177. strfiles := strings.Split(stdout, "\n")
  178. for _, f := range strfiles {
  179. f = strings.TrimSpace(f)
  180. if !utils.IsInStringArray(f, []string{"", ".", ".."}) {
  181. files = append(files, f)
  182. }
  183. }
  184. return files, nil
  185. }
  186. func (p *QemuGuestAgentPartition) GetLocalPath(sPath string, caseInsensitive bool) string {
  187. if sPath == "." {
  188. sPath = ""
  189. }
  190. if !caseInsensitive {
  191. return sPath
  192. }
  193. var fullPath = "/"
  194. pathSegs := strings.Split(sPath, "/")
  195. for _, seg := range pathSegs {
  196. if len(seg) == 0 {
  197. continue
  198. }
  199. var realSeg string
  200. files, err := p.osListDir(fullPath)
  201. if err != nil {
  202. log.Errorf("List dir %s error: %v", sPath, err)
  203. return ""
  204. }
  205. for _, f := range files {
  206. if f == seg || (caseInsensitive && (strings.ToLower(f)) == strings.ToLower(seg)) {
  207. realSeg = f
  208. break
  209. }
  210. }
  211. if len(realSeg) > 0 {
  212. fullPath = path.Join(fullPath, realSeg)
  213. } else {
  214. return ""
  215. }
  216. }
  217. log.Debugf("QGA GetLocalPath %s=>%s", sPath, fullPath)
  218. return fullPath
  219. }
  220. func (p *QemuGuestAgentPartition) FileGetContents(sPath string, caseInsensitive bool) ([]byte, error) {
  221. sPath = p.GetLocalPath(sPath, caseInsensitive)
  222. return p.FileGetContentsByPath(sPath)
  223. }
  224. func (p *QemuGuestAgentPartition) FileGetContentsByPath(sPath string) ([]byte, error) {
  225. res, err := p.agent.FileGetContents(sPath)
  226. if err != nil {
  227. return nil, err
  228. }
  229. return []byte(res), nil
  230. }
  231. func (p *QemuGuestAgentPartition) FilePutContents(sPath, content string, modAppend, caseInsensitive bool) error {
  232. sFilePath := p.GetLocalPath(sPath, caseInsensitive)
  233. if len(sFilePath) > 0 {
  234. sPath = sFilePath
  235. } else {
  236. dirPath := p.GetLocalPath(path.Dir(sPath), caseInsensitive)
  237. if len(dirPath) > 0 {
  238. sPath = path.Join(dirPath, path.Base(sPath))
  239. }
  240. }
  241. if len(sPath) > 0 {
  242. return p.agent.FilePutContents(sPath, content, modAppend)
  243. } else {
  244. return errors.Errorf("Can't put content to empty Path")
  245. }
  246. }
  247. func (p *QemuGuestAgentPartition) Exists(sPath string, caseInsensitive bool) bool {
  248. sPath = p.GetLocalPath(sPath, caseInsensitive)
  249. if len(sPath) > 0 {
  250. exists, err := p.osPathExists(sPath)
  251. if err != nil {
  252. log.Errorf("QGA failed detect path exist %s", err)
  253. }
  254. return exists
  255. }
  256. return false
  257. }
  258. func (p *QemuGuestAgentPartition) Chown(sPath string, uid, gid int, caseInsensitive bool) error {
  259. sPath = p.GetLocalPath(sPath, caseInsensitive)
  260. if len(sPath) == 0 {
  261. return errors.Errorf("Can't get local path: %s", sPath)
  262. }
  263. args := []string{fmt.Sprintf("%d.%d", uid, gid), sPath}
  264. retCode, stdout, stderr, err := p.agent.CommandWithTimeout("chown", args, nil, "", true, -1)
  265. if err != nil {
  266. return errors.Wrap(err, "CommandWithTimeout")
  267. }
  268. if retCode != 0 {
  269. return errors.Errorf("QGA chown failed %s %s, retCode %d", stdout, stderr, retCode)
  270. }
  271. return nil
  272. }
  273. func (p *QemuGuestAgentPartition) Chmod(sPath string, mode uint32, caseInsensitive bool) error {
  274. sPath = p.GetLocalPath(sPath, caseInsensitive)
  275. if sPath == "" {
  276. return nil
  277. }
  278. modeStr := fmt.Sprintf("%o", mode&0777)
  279. args := []string{modeStr, sPath}
  280. retCode, stdout, stderr, err := p.agent.CommandWithTimeout("chmod", args, nil, "", true, -1)
  281. if err != nil {
  282. return errors.Wrap(err, "CommandWithTimeout")
  283. }
  284. if retCode != 0 {
  285. return errors.Errorf("QGA chmod failed %s %s, retCode %d", stdout, stderr, retCode)
  286. }
  287. return nil
  288. }
  289. func (p *QemuGuestAgentPartition) CheckOrAddUser(user, homeDir string, isSys bool) (string, error) {
  290. // check user
  291. retCode, stdout, stderr, err := p.agent.CommandWithTimeout("/bin/cat", []string{"/etc/passwd"}, nil, "", true, -1)
  292. if err != nil {
  293. return "", errors.Wrap(err, "CommandWithTimeout")
  294. }
  295. if retCode != 0 {
  296. return "", errors.Errorf("QGA cat passwd failed %s %s, retCode %d", stdout, stderr, retCode)
  297. }
  298. var exist = false
  299. var realHomeDir = ""
  300. lines := strings.Split(stdout, "\n")
  301. for i := len(lines) - 1; i >= 0; i-- {
  302. userInfos := strings.Split(strings.TrimSpace(lines[i]), ":")
  303. if len(userInfos) < 6 {
  304. continue
  305. }
  306. if userInfos[0] != user {
  307. continue
  308. }
  309. exist = true
  310. realHomeDir = userInfos[5]
  311. break
  312. }
  313. if exist {
  314. args := []string{"-R", "/", "-E", "-1", "-m", "0", "-M", "99999", "-I", "-1", user}
  315. retCode, stdout, stderr, err = p.agent.CommandWithTimeout("chage", args, nil, "", true, -1)
  316. if err != nil {
  317. return "", errors.Wrap(err, "CommandWithTimeout")
  318. }
  319. if retCode != 0 && !strings.Contains(stderr, "not found") {
  320. return "", errors.Errorf("failed chage %s %s", stdout, stderr)
  321. }
  322. if !p.Exists(realHomeDir, false) {
  323. err = p.Mkdir(realHomeDir, 0700, false)
  324. if err != nil {
  325. return "", errors.Wrapf(err, "Mkdir %s", realHomeDir)
  326. }
  327. retCode, stdout, stderr, err = p.agent.CommandWithTimeout("chown", []string{user, realHomeDir}, nil, "", true, -1)
  328. if err != nil {
  329. return "", errors.Wrap(err, "CommandWithTimeout")
  330. }
  331. if retCode != 0 {
  332. return "", errors.Errorf("failed chown %s %s: %s %s, retcode %d", user, realHomeDir, stdout, stderr, retCode)
  333. }
  334. }
  335. return realHomeDir, nil
  336. }
  337. return path.Join(homeDir, user), p.userAdd(user, homeDir, isSys)
  338. }
  339. func (p *QemuGuestAgentPartition) userAdd(user, homeDir string, isSys bool) error {
  340. args := []string{"-m", "-s", "/bin/bash", user}
  341. if isSys {
  342. args = append(args, "-r", "-e", "''", "-f", "'-1'", "-K", "'PASS_MAX_DAYS=-1'")
  343. }
  344. if len(homeDir) > 0 {
  345. args = append(args, "-d", path.Join(homeDir, user))
  346. }
  347. retCode, stdout, stderr, err := p.agent.CommandWithTimeout("useradd", args, nil, "", true, -1)
  348. if err != nil {
  349. return errors.Wrap(err, "CommandWithTimeout")
  350. }
  351. if retCode != 0 {
  352. return errors.Errorf("failed useradd %s: %s %s, retcode %d", user, stdout, stderr, retCode)
  353. }
  354. return nil
  355. }
  356. func (p *QemuGuestAgentPartition) Stat(sPath string, caseInsensitive bool) os.FileInfo {
  357. sPath = p.GetLocalPath(sPath, caseInsensitive)
  358. if len(sPath) == 0 {
  359. return nil
  360. }
  361. args := []string{"-a", "-l", "-n", "-i", "-s", "-d", sPath}
  362. retCode, stdout, stderr, err := p.agent.CommandWithTimeout("ls", args, nil, "", true, -1)
  363. if err != nil {
  364. log.Errorf("CommandWithTimeout %s", err)
  365. return nil
  366. }
  367. if retCode != 0 {
  368. log.Errorf("failed ls %s: %s %s, retcode %d", sPath, stdout, stderr, retCode)
  369. return nil
  370. }
  371. ret := strings.Split(stdout, "\n")
  372. for _, line := range ret {
  373. dat := regexp.MustCompile(`\s+`).Split(strings.TrimSpace(line), -1)
  374. if len(dat) > 7 && ((dat[2][0] != 'l' && dat[len(dat)-1] == sPath) ||
  375. (dat[2][0] == 'l' && dat[len(dat)-3] == sPath)) {
  376. stMode, err := fsdriver.ModeStr2Bin(dat[2])
  377. if err != nil {
  378. log.Errorf("ModeStr2Bin %s", err)
  379. return nil
  380. }
  381. stIno, _ := strconv.Atoi(dat[0])
  382. stUid, _ := strconv.Atoi(dat[4])
  383. stGid, _ := strconv.Atoi(dat[5])
  384. stSize, _ := strconv.Atoi(dat[6])
  385. info := fsdriver.NewFileInfo(
  386. sPath,
  387. int64(stSize),
  388. os.FileMode(stMode),
  389. dat[2][0] == 'd',
  390. &syscall.Stat_t{
  391. Ino: uint64(stIno),
  392. Uid: uint32(stUid),
  393. Gid: uint32(stGid),
  394. Size: int64(stSize),
  395. },
  396. )
  397. return info
  398. }
  399. }
  400. return nil
  401. }
  402. func (p *QemuGuestAgentPartition) Symlink(src, dst string, caseInsensitive bool) error {
  403. odstDir := path.Dir(dst)
  404. if err := p.Mkdir(odstDir, 0755, caseInsensitive); err != nil {
  405. return errors.Wrapf(err, "Mkdir %s", odstDir)
  406. }
  407. if p.Exists(dst, caseInsensitive) {
  408. p.Remove(dst, caseInsensitive)
  409. }
  410. odstDir = p.GetLocalPath(odstDir, caseInsensitive)
  411. dst = path.Join(odstDir, path.Base(dst))
  412. retCode, stdout, stderr, err := p.agent.CommandWithTimeout("ln", []string{"-s", src, dst}, nil, "", true, -1)
  413. if err != nil {
  414. return errors.Wrap(err, "CommandWithTimeout")
  415. }
  416. if retCode != 0 {
  417. return errors.Errorf("failed ln -s %s %s: %s %s, retcode %d", src, dst, stdout, stderr, retCode)
  418. }
  419. return nil
  420. }
  421. func (p *QemuGuestAgentPartition) Passwd(account, password string, caseInsensitive bool) error {
  422. return p.agent.GuestSetUserPassword(account, password, false)
  423. }
  424. func (p *QemuGuestAgentPartition) Mkdir(sPath string, mode int, caseInsensitive bool) error {
  425. segs := strings.Split(sPath, "/")
  426. sp := ""
  427. pPath := p.GetLocalPath("/", caseInsensitive)
  428. var err error
  429. for _, s := range segs {
  430. if len(s) > 0 {
  431. sp = path.Join(sp, s)
  432. vPath := p.GetLocalPath(sp, caseInsensitive)
  433. if len(vPath) == 0 {
  434. err = p.osMkdirP(path.Join(pPath, s), uint32(mode))
  435. pPath = p.GetLocalPath(sp, caseInsensitive)
  436. } else {
  437. pPath = vPath
  438. }
  439. }
  440. }
  441. return err
  442. }
  443. func (p *QemuGuestAgentPartition) osMkdirP(dir string, mode uint32) error {
  444. retCode, stdout, stderr, err := p.agent.CommandWithTimeout("mkdir", []string{"-p", dir}, nil, "", true, -1)
  445. if err != nil {
  446. return errors.Wrap(err, "CommandWithTimeout")
  447. }
  448. if retCode != 0 {
  449. return errors.Errorf("mkdir -p %s: %s %s: retCode: %d", dir, stdout, stderr, retCode)
  450. }
  451. return p.Chmod(dir, mode, false)
  452. }
  453. func (p *QemuGuestAgentPartition) ListDir(sPath string, caseInsensitive bool) []string {
  454. sPath = p.GetLocalPath(sPath, caseInsensitive)
  455. if len(sPath) > 0 {
  456. ret, err := p.osListDir(sPath)
  457. if err != nil {
  458. log.Errorf("list dir for %s: %v", sPath, err)
  459. return nil
  460. }
  461. return ret
  462. }
  463. return nil
  464. }
  465. func (p *QemuGuestAgentPartition) Remove(sPath string, caseInsensitive bool) {
  466. sPath = p.GetLocalPath(sPath, caseInsensitive)
  467. if len(sPath) > 0 {
  468. retCode, stdout, stderr, err := p.agent.CommandWithTimeout("rm", []string{sPath}, nil, "", true, -1)
  469. if err != nil {
  470. log.Errorf("remove %s: %s", sPath, err)
  471. return
  472. }
  473. if retCode != 0 {
  474. log.Errorf("remove %s: %s %s: retCode: %d", sPath, stdout, stderr, retCode)
  475. return
  476. }
  477. }
  478. }
  479. func (p *QemuGuestAgentPartition) Cleandir(dir string, keepdir, caseInsensitive bool) error {
  480. sPath := p.GetLocalPath(dir, caseInsensitive)
  481. if len(sPath) > 0 {
  482. retCode, stdout, stderr, err := p.agent.CommandWithTimeout("rm", []string{"-rf", sPath}, nil, "", true, -1)
  483. if err != nil {
  484. return errors.Wrapf(err, "remove -rf %s", sPath)
  485. }
  486. if retCode != 0 {
  487. return errors.Wrapf(err, "remove -rf %s: %s %s: retCode: %d", sPath, stdout, stderr, retCode)
  488. }
  489. }
  490. return nil
  491. }
  492. func (*QemuGuestAgentPartition) Zerofiles(dir string, caseInsensitive bool) error {
  493. return nil
  494. }
  495. func (*QemuGuestAgentPartition) SupportSerialPorts() bool {
  496. return false
  497. }
  498. func (*QemuGuestAgentPartition) GetPartDev() string {
  499. return "QGA"
  500. }
  501. func (*QemuGuestAgentPartition) IsMounted() bool {
  502. return true
  503. }
  504. func (*QemuGuestAgentPartition) Mount() bool {
  505. return true
  506. }
  507. func (*QemuGuestAgentPartition) MountPartReadOnly() bool {
  508. return false
  509. }
  510. func (*QemuGuestAgentPartition) Umount() error {
  511. return nil
  512. }
  513. func (*QemuGuestAgentPartition) GetMountPath() string {
  514. return ""
  515. }
  516. func (*QemuGuestAgentPartition) IsReadonly() bool {
  517. return false
  518. }
  519. func (*QemuGuestAgentPartition) GetPhysicalPartitionType() string {
  520. return ""
  521. }
  522. func (*QemuGuestAgentPartition) Zerofree() {
  523. }
  524. func (*QemuGuestAgentPartition) GenerateSshHostKeys() error {
  525. return nil
  526. }