kvmpart.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  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 kvmpart
  15. import (
  16. "context"
  17. "fmt"
  18. "math/rand"
  19. "os"
  20. "path/filepath"
  21. "strings"
  22. "time"
  23. "yunion.io/x/log"
  24. "yunion.io/x/pkg/errors"
  25. "yunion.io/x/pkg/utils"
  26. "yunion.io/x/onecloud/pkg/cloudcommon/consts"
  27. "yunion.io/x/onecloud/pkg/hostman/diskutils/fsutils"
  28. "yunion.io/x/onecloud/pkg/hostman/guestfs"
  29. "yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver"
  30. "yunion.io/x/onecloud/pkg/util/btrfsutils"
  31. "yunion.io/x/onecloud/pkg/util/fileutils2"
  32. "yunion.io/x/onecloud/pkg/util/procutils"
  33. "yunion.io/x/onecloud/pkg/util/xfsutils"
  34. )
  35. type SKVMGuestDiskPartition struct {
  36. *SLocalGuestFS
  37. partDev string
  38. fs string
  39. uuid string
  40. readonly bool
  41. sourceDev string
  42. IsLVMPart bool
  43. }
  44. var _ fsdriver.IDiskPartition = &SKVMGuestDiskPartition{}
  45. func NewKVMGuestDiskPartition(devPath, sourceDev string, isLVM bool) *SKVMGuestDiskPartition {
  46. var res = new(SKVMGuestDiskPartition)
  47. res.partDev = devPath
  48. res.fs = res.getFsFormat()
  49. res.sourceDev = sourceDev
  50. res.IsLVMPart = isLVM
  51. fileutils2.CleanFailedMountpoints()
  52. mountPath := fmt.Sprintf("/tmp/%s", strings.Replace(devPath, "/", "_", -1))
  53. res.SLocalGuestFS = NewLocalGuestFS(mountPath)
  54. return res
  55. }
  56. func (p *SKVMGuestDiskPartition) GetPhysicalPartitionType() string {
  57. dev := p.partDev
  58. if p.IsLVMPart {
  59. dev = p.sourceDev
  60. }
  61. idxP := strings.LastIndexByte(dev, 'p')
  62. if idxP > 0 {
  63. dev = dev[:idxP]
  64. } else {
  65. dev = strings.TrimRight(dev, "0123456789")
  66. }
  67. cmd := fmt.Sprintf(`fdisk -l %s | grep "Disk.*label type:"`, dev)
  68. output, err := procutils.NewCommand("sh", "-c", cmd).Output()
  69. if err != nil {
  70. log.Errorf("get disk label type error %s", output)
  71. return ""
  72. }
  73. idx := strings.Index(string(output), "Disk label type:")
  74. tp := strings.TrimSpace(string(output)[idx+len("Disk label type:"):])
  75. if tp == "dos" {
  76. return "mbr"
  77. } else {
  78. return tp
  79. }
  80. }
  81. func (p *SKVMGuestDiskPartition) GetPartDev() string {
  82. return p.partDev
  83. }
  84. func (p *SKVMGuestDiskPartition) IsReadonly() bool {
  85. return guestfs.IsPartitionReadonly(p)
  86. }
  87. func (p *SKVMGuestDiskPartition) getFsFormat() string {
  88. return fsutils.GetFsFormat(p.partDev)
  89. }
  90. func (p *SKVMGuestDiskPartition) MountPartReadOnly() bool {
  91. if len(p.fs) == 0 || utils.IsInStringArray(p.fs, []string{"swap", "btrfs"}) {
  92. return false
  93. }
  94. // no fsck, becasue read only
  95. err := p.mount(true)
  96. if err != nil {
  97. log.Errorf("SKVMGuestDiskPartition mount as readonly error: %s", err)
  98. return false
  99. }
  100. p.readonly = true
  101. return true
  102. }
  103. func (p *SKVMGuestDiskPartition) Mount() bool {
  104. if len(p.fs) == 0 || utils.IsInStringArray(p.fs, []string{"swap"}) {
  105. log.Errorf("Mount fs failed: unsupport fs %s on %s", p.fs, p.partDev)
  106. return false
  107. }
  108. err := p.fsck()
  109. if err != nil {
  110. log.Errorf("SKVMGuestDiskPartition fsck error: %s", err)
  111. // return false
  112. }
  113. err = p.mount(false)
  114. if err != nil {
  115. log.Errorf("SKVMGuestDiskPartition mount error: %s", err)
  116. } else if p.fs == "btrfs" {
  117. // try mount btrfs subvoume
  118. err := btrfsutils.MountSubvols(p.partDev, p.mountPath)
  119. if err != nil {
  120. log.Errorf("SKVMGuestDiskPartition mount btrfs error %s", err)
  121. }
  122. }
  123. if p.IsReadonly() {
  124. log.Errorf("SKVMGuestDiskPartition %s is readonly, try mount as ro", p.partDev)
  125. p.Umount()
  126. err = p.mount(true)
  127. if err != nil {
  128. log.Errorf("SKVMGuestDiskPartition mount as ro error %s", err)
  129. return false
  130. } else {
  131. p.readonly = true
  132. }
  133. }
  134. log.Infof("mount fs %s on %s successfully", p.fs, p.partDev)
  135. return true
  136. }
  137. func (p *SKVMGuestDiskPartition) mount(readonly bool) error {
  138. if output, err := procutils.NewCommand("mkdir", "-p", p.mountPath).Output(); err != nil {
  139. return errors.Wrapf(err, "mkdir %s failed: %s", p.mountPath, output)
  140. }
  141. var cmds = []string{"mount"}
  142. var opt, fsType string
  143. if readonly {
  144. opt = "ro"
  145. }
  146. fsType = p.fs
  147. if fsType == "ntfs" {
  148. fsType = "ntfs-3g"
  149. if !readonly {
  150. opt = "recover,remove_hiberfile,noatime,windows_names"
  151. }
  152. } else if fsType == "hfsplus" && !readonly {
  153. opt = "force,rw"
  154. }
  155. if len(fsType) > 0 {
  156. cmds = append(cmds, "-t", fsType)
  157. }
  158. if len(opt) > 0 {
  159. cmds = append(cmds, "-o", opt)
  160. }
  161. cmds = append(cmds, p.partDev, p.mountPath)
  162. p.acquireXFS(fsType)
  163. var err error
  164. retrier := func(utils.FibonacciRetrier) (bool, error) {
  165. var errChan = make(chan error)
  166. go func() {
  167. output, err := procutils.NewCommand(cmds[0], cmds[1:]...).Output()
  168. if err == nil {
  169. errChan <- nil
  170. } else {
  171. log.Errorf("mount fail: %s %s", err, output)
  172. time.Sleep(time.Millisecond * time.Duration(100+rand.Intn(400)))
  173. errChan <- err
  174. }
  175. }()
  176. // mount timeout
  177. waitTime := time.Second * 30
  178. if fsType == "ntfs-3g" {
  179. waitTime = time.Second * 5
  180. }
  181. select {
  182. case err = <-errChan:
  183. if err != nil {
  184. return false, err
  185. }
  186. return true, nil
  187. case <-time.After(waitTime):
  188. if fsType == "ntfs-3g" {
  189. if err = procutils.NewCommand("mountpoint", p.mountPath).Run(); err != nil {
  190. // mountpath is not a mountpoint
  191. return false, err
  192. } else {
  193. // ntfs already mounted, but maybe in buildroot yunion os Failed to daemonize.
  194. return true, nil
  195. }
  196. } else {
  197. return false, errors.Errorf("mount timeout")
  198. }
  199. }
  200. }
  201. _, err = utils.NewFibonacciRetrierMaxTries(3, retrier).Start(context.Background())
  202. if err != nil {
  203. p.releaseXFS(fsType)
  204. return errors.Wrap(err, "mount failed")
  205. }
  206. return nil
  207. }
  208. func (p *SKVMGuestDiskPartition) acquireXFS(fsType string) {
  209. if fsType == "xfs" {
  210. uuids, _ := fileutils2.GetDevUuid(p.partDev)
  211. p.uuid = uuids["UUID"]
  212. if len(p.uuid) > 0 {
  213. xfsutils.LockXfsPartition(p.uuid)
  214. }
  215. }
  216. }
  217. func (p *SKVMGuestDiskPartition) releaseXFS(fsType string) {
  218. if fsType == "xfs" && len(p.uuid) > 0 {
  219. xfsutils.UnlockXfsPartition(p.uuid)
  220. }
  221. }
  222. func (p *SKVMGuestDiskPartition) fsck() error {
  223. var checkCmd, fixCmd []string
  224. switch p.fs {
  225. case "btrfs":
  226. checkCmd = []string{"fsck.btrfs", "--readonly", p.partDev}
  227. fixCmd = []string{"fsck.btrfs", "--repair", p.partDev}
  228. case "hfsplus":
  229. checkCmd = []string{"fsck.hfsplus", "-q", p.partDev}
  230. fixCmd = []string{"fsck.hfsplus", "-fpy", p.partDev}
  231. case "ext2", "ext3", "ext4":
  232. checkCmd = []string{"e2fsck", "-n", p.partDev}
  233. fixCmd = []string{"e2fsck", "-fp", p.partDev}
  234. case "ntfs":
  235. checkCmd = []string{"ntfsfix", "-n", p.partDev}
  236. fixCmd = []string{"ntfsfix", p.partDev}
  237. case "f2fs":
  238. checkCmd = []string{"fsck.f2fs", "-f", p.partDev}
  239. fixCmd = []string{"fsck.f2fs", "-a", p.partDev}
  240. }
  241. if len(checkCmd) > 0 {
  242. _, err := procutils.NewCommand(checkCmd[0], checkCmd[1:]...).Output()
  243. if err != nil {
  244. log.Warningf("FS %s dirty, try to repair ...", p.partDev)
  245. for i := 0; i < 3; i++ {
  246. output, err := procutils.NewCommand(fixCmd[0], fixCmd[1:]...).Output()
  247. if err == nil {
  248. break
  249. } else {
  250. log.Errorf("Try to fix partition %s failed: %s, %s", fixCmd, err, output)
  251. continue
  252. }
  253. }
  254. }
  255. }
  256. return nil
  257. }
  258. func (p *SKVMGuestDiskPartition) Exists(sPath string, caseInsensitive bool) bool {
  259. sPath = p.GetLocalPath(sPath, caseInsensitive)
  260. if len(sPath) > 0 {
  261. return fileutils2.Exists(sPath)
  262. }
  263. return false
  264. }
  265. func (p *SKVMGuestDiskPartition) IsMounted() bool {
  266. if !fileutils2.Exists(p.mountPath) {
  267. return false
  268. }
  269. output, err := procutils.NewCommand("mountpoint", p.mountPath).Output()
  270. if err == nil {
  271. return true
  272. } else {
  273. log.Errorf("%s is not a mountpoint: %s", p.mountPath, output)
  274. return false
  275. }
  276. }
  277. func (p *SKVMGuestDiskPartition) Umount() error {
  278. if !p.IsMounted() {
  279. return nil
  280. }
  281. defer p.releaseXFS(p.fs)
  282. if p.fs == "btrfs" {
  283. // first try unmount btrfs subvols
  284. err := btrfsutils.UnmountSubvols(p.mountPath)
  285. if err != nil {
  286. log.Errorf("SKVMGuestDiskPartition unmount btrfs error %s", err)
  287. }
  288. }
  289. if _, err := procutils.NewCommand("blockdev", "--flushbufs", p.partDev).Output(); err != nil {
  290. log.Warningf("blockdev --flushbufs %s error: %v", p.partDev, err)
  291. }
  292. var tries = 0
  293. var err error
  294. var out []byte
  295. for tries < 10 {
  296. tries += 1
  297. log.Infof("umount %s: %s", p.partDev, p.mountPath)
  298. out, err = procutils.NewCommand("umount", p.mountPath).Output()
  299. if err == nil {
  300. if err := os.Remove(p.mountPath); err != nil {
  301. log.Warningf("remove mount path %s error: %v", p.mountPath, err)
  302. }
  303. log.Infof("umount %s successfully", p.partDev)
  304. return nil
  305. } else {
  306. lsofout, err := procutils.NewCommand("lsof", p.mountPath).Output()
  307. log.Infof("unmount path %s lsof %s", p.mountPath, string(lsofout))
  308. log.Warningf("failed umount %s: %s %s", p.partDev, err, out)
  309. time.Sleep(time.Second * 3)
  310. }
  311. }
  312. return errors.Wrapf(err, "umount %s", p.mountPath)
  313. }
  314. func (p *SKVMGuestDiskPartition) Zerofree() {
  315. if !p.IsMounted() {
  316. switch p.fs {
  317. case "swap":
  318. p.zerofreeSwap()
  319. case "ext2", "ext3", "ext4":
  320. p.zerofreeExt()
  321. case "ntfs":
  322. p.zerofreeNtfs()
  323. // case "xfs":
  324. // p.zerofreeFs("xfs")
  325. }
  326. }
  327. }
  328. func (p *SKVMGuestDiskPartition) zerofreeSwap() {
  329. uuids, _ := fileutils2.GetDevUuid(p.partDev)
  330. output, err := procutils.NewCommand("shred", "-n", "0", "-z", p.partDev).Output()
  331. if err != nil {
  332. log.Errorf("zerofree swap error: %s, %s", err, output)
  333. return
  334. }
  335. cmd := []string{"mkswap"}
  336. if uuid, ok := uuids["UUID"]; ok {
  337. cmd = append(cmd, "-U", uuid)
  338. }
  339. cmd = append(cmd, p.partDev)
  340. output, err = procutils.NewCommand(cmd[0], cmd[1:]...).Output()
  341. if err != nil {
  342. log.Errorf("zerofree swap error: %s, %s", err, output)
  343. }
  344. }
  345. func (p *SKVMGuestDiskPartition) zerofreeExt() {
  346. output, err := procutils.NewCommand("zerofree", p.partDev).Output()
  347. if err != nil {
  348. log.Errorf("zerofree ext error: %s, %s", err, output)
  349. return
  350. }
  351. }
  352. func (p *SKVMGuestDiskPartition) zerofreeNtfs() {
  353. err := procutils.NewCommand("ntfswipe", "-f", "-l", "-m", "-p", "-s", "-q", p.partDev).Run()
  354. if err != nil {
  355. log.Errorf("zerofree ntfs error: %s", err)
  356. return
  357. }
  358. }
  359. func (p *SKVMGuestDiskPartition) zerofreeFs(fs string) {
  360. err := p.zerofreeFsInternal(fs)
  361. if err != nil {
  362. log.Errorf("zerofreeFs %s %s: %s", p.partDev, fs, err)
  363. }
  364. }
  365. func (p *SKVMGuestDiskPartition) zerofreeFsInternal(fs string) error {
  366. uuids, _ := fileutils2.GetDevUuid(p.partDev)
  367. backupDirPath, err := os.MkdirTemp(consts.DeployTempDir(), "backup")
  368. if err != nil {
  369. return errors.Wrap(err, "MkdirTemp")
  370. }
  371. defer func() {
  372. cmd := []string{"rm", "-fr", backupDirPath}
  373. output, err := procutils.NewCommand(cmd[0], cmd[1:]...).Output()
  374. if err != nil {
  375. log.Errorf("fail to remove %s %s: %s", backupDirPath, output, err)
  376. }
  377. }()
  378. backupPath := filepath.Join(backupDirPath, "backup.tar.gz")
  379. err = func() error {
  380. // mount partition
  381. if !p.Mount() {
  382. return errors.Wrap(errors.ErrInvalidStatus, "fail to mount")
  383. }
  384. defer p.Umount()
  385. // backup
  386. cmd := []string{"tar", "-cpzf", backupPath, "--one-file-system", "-C", p.mountPath, "."}
  387. output, err := procutils.NewCommand(cmd[0], cmd[1:]...).Output()
  388. if err != nil {
  389. return errors.Wrapf(err, "exec %s output %s", cmd, output)
  390. }
  391. return nil
  392. }()
  393. if err != nil {
  394. return errors.Wrap(err, "backup")
  395. }
  396. // zero partition
  397. output, err := procutils.NewCommand("shred", "-n", "0", "-z", p.partDev).Output()
  398. if err != nil {
  399. return errors.Wrapf(err, "shred output %s", output)
  400. }
  401. // make partition
  402. uuid := uuids["UUID"]
  403. err = fsutils.FormatPartition(p.partDev, fs, uuid, nil)
  404. if err != nil {
  405. return errors.Wrapf(err, "FormatPartition %s %s %s", p.partDev, fs, uuid)
  406. }
  407. err = func() error {
  408. // mount partition
  409. if !p.Mount() {
  410. return errors.Wrap(errors.ErrInvalidStatus, "fail to mount")
  411. }
  412. // umount
  413. defer p.Umount()
  414. // copy back data
  415. cmd := []string{"tar", "-xpzf", backupPath, "--numeric-owner", "-C", p.mountPath}
  416. output, err := procutils.NewCommand(cmd[0], cmd[1:]...).Output()
  417. if err != nil {
  418. return errors.Wrapf(err, "exec %s output %s", cmd, output)
  419. }
  420. return nil
  421. }()
  422. if err != nil {
  423. return errors.Wrap(err, "restore")
  424. }
  425. return nil
  426. }