guestfish.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  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 guestfish
  15. import (
  16. "bufio"
  17. "fmt"
  18. "io/ioutil"
  19. "os"
  20. "os/exec"
  21. "runtime/debug"
  22. "strings"
  23. "sync"
  24. "time"
  25. "github.com/creack/pty"
  26. "yunion.io/x/log"
  27. "yunion.io/x/pkg/errors"
  28. "yunion.io/x/pkg/sortedmap"
  29. )
  30. type Guestfish struct {
  31. *exec.Cmd
  32. stdoutScanner *bufio.Scanner
  33. stderrScanner *bufio.Scanner
  34. pty *os.File
  35. lock sync.Mutex
  36. label string
  37. stdout *os.File
  38. stdoutW *os.File
  39. stderr *os.File
  40. stderrW *os.File
  41. alive bool
  42. }
  43. const guestFishToken = "><fs>"
  44. func NewGuestfish() (*Guestfish, error) {
  45. gf := &Guestfish{Cmd: exec.Command("guestfish")}
  46. stdout, stdoutW, err := os.Pipe()
  47. if err != nil {
  48. return nil, err
  49. }
  50. stderr, stderrW, err := os.Pipe()
  51. if err != nil {
  52. return nil, err
  53. }
  54. gf.stdout = stdout
  55. gf.stdoutW = stdoutW
  56. gf.stderr = stderr
  57. gf.stderrW = stderrW
  58. gf.Cmd.Stdout = stdoutW
  59. gf.Cmd.Stderr = stderrW
  60. gf.stdoutScanner = bufio.NewScanner(stdout)
  61. gf.stderrScanner = bufio.NewScanner(stderr)
  62. pty, err := pty.Start(gf.Cmd)
  63. if err != nil {
  64. return nil, errors.Wrap(err, "start Guestfish")
  65. }
  66. /* pty handle stdin */
  67. gf.pty = pty
  68. /* exec guestfish run command */
  69. if err = gf.Run(); err != nil {
  70. return nil, err
  71. }
  72. gf.alive = true
  73. return gf, nil
  74. }
  75. func (fish *Guestfish) IsAlive() bool {
  76. return fish.alive
  77. }
  78. func (fish *Guestfish) execute(cmd string) ([]string, error) {
  79. fish.lock.Lock()
  80. defer fish.lock.Unlock()
  81. log.Debugf("exec command: %s", cmd)
  82. _, err := fish.pty.WriteString(cmd + "\n\n")
  83. if err != nil {
  84. return nil, errors.Wrapf(err, "exec cmd %s", cmd)
  85. }
  86. return fish.fetch()
  87. }
  88. func (fish *Guestfish) fetch() ([]string, error) {
  89. var (
  90. stdout = make([]string, 0)
  91. )
  92. var tokenMeeted = false
  93. for fish.stdoutScanner.Scan() {
  94. line := fish.stdoutScanner.Text()
  95. log.Debugf("Guestfish stdoutScanner: %s", line)
  96. if strings.HasPrefix(line, guestFishToken) {
  97. if !tokenMeeted {
  98. tokenMeeted = true
  99. continue
  100. }
  101. log.Debugf("fetch success")
  102. break
  103. }
  104. stdout = append(stdout, line)
  105. }
  106. if err := fish.stdoutScanner.Err(); err != nil {
  107. log.Errorf("scan guestfish stdoutScanner error %s", err)
  108. fish.Quit()
  109. return nil, err
  110. }
  111. fish.stderr.SetReadDeadline(time.Now().Add(time.Second * 1))
  112. output, err := ioutil.ReadAll(fish.stderr)
  113. if err != nil && !strings.Contains(err.Error(), "i/o timeout") {
  114. log.Errorf("scan guestfish stderrScanner error %s", err)
  115. fish.Quit()
  116. return nil, err
  117. }
  118. var stderrErr error
  119. if len(output) > 0 {
  120. stderrErr = errors.Errorf("%s", string(output))
  121. }
  122. return stdout, stderrErr
  123. }
  124. /* Fetch error message from stderrScanner, until got ><fs> from stdoutScanner */
  125. func (fish *Guestfish) fetchError() error {
  126. _, err := fish.fetch()
  127. return err
  128. }
  129. func (fish *Guestfish) Run() error {
  130. _, err := fish.execute("run")
  131. return err
  132. }
  133. func (fish *Guestfish) Quit() error {
  134. checkError := func(err error) {
  135. if err != nil {
  136. log.Errorln(err)
  137. }
  138. }
  139. defer func() {
  140. if r := recover(); r != nil {
  141. log.Errorf("fish quit faild %s \nstack: %s", r, debug.Stack())
  142. }
  143. }()
  144. fish.lock.Lock()
  145. defer fish.lock.Unlock()
  146. log.Debugf("exec command: %s", "quit")
  147. _, err := fish.pty.WriteString("quit\n\n")
  148. if err != nil {
  149. return errors.Wrap(err, "exec cmd kill-subprocess and quit")
  150. }
  151. fish.alive = false
  152. if err := fish.Cmd.Wait(); err != nil {
  153. log.Errorf("failed wait guestfish %s", err)
  154. }
  155. checkError(fish.stdout.Close())
  156. checkError(fish.stderr.Close())
  157. checkError(fish.stdoutW.Close())
  158. checkError(fish.stderrW.Close())
  159. checkError(fish.pty.Close())
  160. return err
  161. }
  162. func (fish *Guestfish) AddDrive(path, label string, readonly bool) error {
  163. cmd := fmt.Sprintf("add-drive %s label:%s", path, label)
  164. if readonly {
  165. cmd += " readonly:true"
  166. }
  167. _, err := fish.execute(cmd)
  168. if err != nil {
  169. return err
  170. }
  171. fish.label = label
  172. return nil
  173. }
  174. func (fish *Guestfish) RemoveDrive() error {
  175. if len(fish.label) == 0 {
  176. return errors.Errorf("no drive add")
  177. }
  178. _, err := fish.execute(fmt.Sprintf("remove-drive %s", fish.label))
  179. if err != nil {
  180. return err
  181. }
  182. fish.label = ""
  183. return err
  184. }
  185. func (fish *Guestfish) ListFilesystems() (*sortedmap.SSortedMap, error) {
  186. output, err := fish.execute("list-filesystems")
  187. if err != nil {
  188. return nil, err
  189. }
  190. return fish.parseListFilesystemsOutput(output), nil
  191. }
  192. func (fish *Guestfish) parseListFilesystemsOutput(output []string) *sortedmap.SSortedMap {
  193. /* /dev/sda1: xfs
  194. /dev/centos/root: xfs
  195. /dev/centos/swap: swap */
  196. res := sortedmap.SSortedMap{}
  197. for i := 0; i < len(output); i++ {
  198. line := output[i]
  199. log.Debugf("line %s", line)
  200. segs := strings.Split(line, ":")
  201. log.Debugf("parse line of list filesystems: %#v", segs)
  202. if len(segs) != 2 {
  203. log.Warningf("Guestfish: parse list filesystem got unwanted line: %s", line)
  204. }
  205. res = sortedmap.Add(res, strings.TrimSpace(segs[0]), strings.TrimSpace(segs[1]))
  206. }
  207. return &res
  208. }
  209. func (fish *Guestfish) ListDevices() ([]string, error) {
  210. return fish.execute("list-devices")
  211. }
  212. func (fish *Guestfish) Mount(partition string) error {
  213. _, err := fish.execute(fmt.Sprintf("mount %s /", partition))
  214. return err
  215. }
  216. func (fish *Guestfish) MountLocal(localmountpoint string, readonly bool) error {
  217. cmd := fmt.Sprintf("mount-local %s", localmountpoint)
  218. if readonly {
  219. cmd += " readonly:true"
  220. }
  221. _, err := fish.execute(cmd)
  222. return err
  223. }
  224. func (fish *Guestfish) Umount(partition string) error {
  225. _, err := fish.execute("umount")
  226. return err
  227. }
  228. func (fish *Guestfish) UmountLocal() error {
  229. _, err := fish.execute("umount-local")
  230. return err
  231. }
  232. /* This should only be called after "mount_local" returns successfully.
  233. * The call will not return until the filesystem is unmounted. */
  234. func (fish *Guestfish) MountLocalRun() error {
  235. _, err := fish.execute("mount-local-run")
  236. return err
  237. }
  238. /* Clears the LVM cache and performs a volume group scan. */
  239. func (fish *Guestfish) LvmClearFilter() error {
  240. _, err := fish.execute("lvm-clear-filter")
  241. return err
  242. }
  243. func (fish *Guestfish) Lvs() ([]string, error) {
  244. return fish.execute("lvs")
  245. }
  246. func (fish *Guestfish) SfdiskL(dev string) ([]string, error) {
  247. return fish.execute(fmt.Sprintf("sfdisk-l %s", dev))
  248. }
  249. func (fish *Guestfish) Fsck(dev, fs string) error {
  250. out, err := fish.execute(fmt.Sprintf("fsck %s %s", fs, dev))
  251. log.Infof("FSCK ret code: %v", out)
  252. return err
  253. }
  254. func (fish *Guestfish) Ntfsfix(dev string) error {
  255. out, err := fish.execute(fmt.Sprintf("ntfsfix %s", dev))
  256. log.Infof("NTFSFIX ret code: %v", out)
  257. return err
  258. }
  259. func (fish *Guestfish) Zerofree(dev string) error {
  260. _, err := fish.execute(fmt.Sprintf("zerofree %s", dev))
  261. return err
  262. }
  263. func (fish *Guestfish) ZeroFreeSpace(dir string) error {
  264. _, err := fish.execute(fmt.Sprintf("zero-free-space %s", dir))
  265. return err
  266. }
  267. func (fish *Guestfish) Blkid(partDev string) ([]string, error) {
  268. return fish.execute(fmt.Sprintf("blkid %s", partDev))
  269. }
  270. func (fish *Guestfish) Mkswap(partDev, uuid, label string) error {
  271. cmd := fmt.Sprintf("mkswap %s", partDev)
  272. if len(uuid) > 0 {
  273. cmd += fmt.Sprintf(" uuid:%s", uuid)
  274. }
  275. if len(label) > 0 {
  276. cmd += fmt.Sprintf(" label:%s", label)
  277. }
  278. _, err := fish.execute(cmd)
  279. return err
  280. }
  281. func (fish *Guestfish) Mkfs(dev, fs string) error {
  282. _, err := fish.execute(fmt.Sprintf("mkfs %s %s", fs, dev))
  283. return err
  284. }
  285. func (fish *Guestfish) PartDisk(dev, diskType string) error {
  286. _, err := fish.execute(fmt.Sprintf("part-disk %s %s", dev, diskType))
  287. return err
  288. }