sshpart.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  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 sshpart
  15. import (
  16. "fmt"
  17. "os"
  18. "path"
  19. "regexp"
  20. "strconv"
  21. "strings"
  22. "syscall"
  23. "time"
  24. "yunion.io/x/log"
  25. "yunion.io/x/pkg/errors"
  26. "yunion.io/x/pkg/utils"
  27. "yunion.io/x/onecloud/pkg/baremetal/utils/disktool"
  28. "yunion.io/x/onecloud/pkg/compute/baremetal"
  29. "yunion.io/x/onecloud/pkg/hostman/guestfs"
  30. "yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver"
  31. "yunion.io/x/onecloud/pkg/util/ssh"
  32. stringutils "yunion.io/x/onecloud/pkg/util/stringutils2"
  33. )
  34. type SSHPartition struct {
  35. term ISSHClient // *ssh.Client
  36. partDev string
  37. mountPath string
  38. isLVM bool
  39. }
  40. var _ fsdriver.IDiskPartition = &SSHPartition{}
  41. func NewSSHPartition(term ISSHClient, partDev string, isLVM bool) *SSHPartition {
  42. p := new(SSHPartition)
  43. p.term = term
  44. p.partDev = partDev
  45. p.isLVM = isLVM
  46. p.mountPath = fmt.Sprintf("/tmp/%s", strings.Replace(p.partDev, "/", "_", -1))
  47. return p
  48. }
  49. func (p *SSHPartition) GetMountPath() string {
  50. return p.mountPath
  51. }
  52. func (p *SSHPartition) GetFsFormat() (string, error) {
  53. var cmd string
  54. if strings.HasPrefix(p.partDev, "/dev/md") {
  55. cmd = fmt.Sprintf("blkid -o value -s TYPE %s", p.partDev)
  56. } else {
  57. cmd = fmt.Sprintf("/lib/mos/partfs.sh %s", p.partDev)
  58. }
  59. ret, err := p.term.Run(cmd)
  60. if err != nil {
  61. return "", err
  62. }
  63. return strings.TrimSpace(ret[0]), nil
  64. }
  65. func (p *SSHPartition) osChmod(path string, mode uint32) error {
  66. cmd := fmt.Sprintf("chmod %o %s", (mode & 0777), path)
  67. _, err := p.term.Run(cmd)
  68. return err
  69. }
  70. func (p *SSHPartition) osMkdirP(dir string, mode uint32) error {
  71. cmd := fmt.Sprintf("mkdir -p %s", dir)
  72. _, err := p.term.Run(cmd)
  73. if err != nil {
  74. return err
  75. }
  76. if mode != 0 {
  77. return p.osChmod(dir, mode)
  78. }
  79. return nil
  80. }
  81. func (p *SSHPartition) Mkdir(sPath string, mode int, caseInsensitive bool) error {
  82. segs := strings.Split(sPath, "/")
  83. sp := ""
  84. pPath := p.GetLocalPath("/", caseInsensitive)
  85. var err error
  86. for _, s := range segs {
  87. if len(s) > 0 {
  88. sp = path.Join(sp, s)
  89. vPath := p.GetLocalPath(sp, caseInsensitive)
  90. if len(vPath) == 0 {
  91. err = p.osMkdirP(path.Join(pPath, s), uint32(mode))
  92. pPath = p.GetLocalPath(sp, caseInsensitive)
  93. } else {
  94. pPath = vPath
  95. }
  96. }
  97. }
  98. return err
  99. }
  100. func (p *SSHPartition) osRmDir(path string) error {
  101. _, err := p.term.Run(fmt.Sprintf("rm -fr %s", path))
  102. return err
  103. }
  104. func (p *SSHPartition) osPathExists(path string) bool {
  105. // test file or symbolic link exists
  106. _, err := p.term.Run(fmt.Sprintf("test -e %s || test -L %s", path, path))
  107. if err != nil {
  108. return false
  109. }
  110. return true
  111. }
  112. func (p *SSHPartition) osSymlink(src, dst string) error {
  113. _, err := p.term.Run(fmt.Sprintf("ln -s %s %s", src, dst))
  114. return err
  115. }
  116. func (p *SSHPartition) MountPartReadOnly() bool {
  117. return false // Not implement
  118. }
  119. func (p *SSHPartition) IsReadonly() bool {
  120. return false // sshpart not implement mount as readonly
  121. }
  122. func (p *SSHPartition) GetPhysicalPartitionType() string {
  123. return "" // Not implement
  124. }
  125. func (p *SSHPartition) Mount() bool {
  126. if err := p.osMkdirP(p.mountPath, 0); err != nil {
  127. log.Errorf("SSHPartition mount error: %s", err)
  128. return false
  129. }
  130. fs, err := p.GetFsFormat()
  131. if err != nil {
  132. log.Errorf("SSHPartition mount error: %s", err)
  133. return false
  134. }
  135. fstr := ""
  136. if fs == "ntfs" {
  137. fstr = "-t ntfs-3g"
  138. }
  139. cmd := fmt.Sprintf("mount %s -o sync %s %s", fstr, p.partDev, p.mountPath)
  140. log.Infof("Do mount %s", cmd)
  141. _, err = p.term.Run(cmd)
  142. if err != nil {
  143. p.osRmDir(p.mountPath)
  144. log.Errorf("SSHPartition mount error: %s", err)
  145. return false
  146. }
  147. return true
  148. }
  149. func (p *SSHPartition) Umount() error {
  150. if !p.IsMounted() {
  151. log.Warningf("%s is not mounted", p.mountPath)
  152. return nil
  153. }
  154. var err error
  155. for tries := 0; tries < 10; tries++ {
  156. cmds := []string{
  157. "sync",
  158. "/sbin/sysctl -w vm.drop_caches=3",
  159. fmt.Sprintf("/bin/umount %s", p.mountPath),
  160. fmt.Sprintf("/sbin/hdparm -f %s", p.partDev),
  161. //fmt.Sprintf("/usr/bin/sg_sync %s", p.part.GetDisk().GetDev()),
  162. }
  163. _, err = p.term.Run(cmds...)
  164. if err != nil {
  165. log.Errorf("umount %s error: %v", p.mountPath, err)
  166. time.Sleep(1 * time.Second)
  167. } else {
  168. if err := p.osRmDir(p.mountPath); err != nil {
  169. log.Warningf("remove mount path %s: %v", p.mountPath, err)
  170. }
  171. return nil
  172. }
  173. }
  174. return err
  175. }
  176. func (p *SSHPartition) IsMounted() bool {
  177. if !p.osPathExists(p.mountPath) {
  178. return false
  179. }
  180. _, err := p.term.Run(fmt.Sprintf("mountpoint %s", p.mountPath))
  181. if err != nil {
  182. return false
  183. }
  184. return true
  185. }
  186. func (p *SSHPartition) GetPartDev() string {
  187. return ""
  188. }
  189. func (p *SSHPartition) Chmod(sPath string, mode uint32, caseI bool) error {
  190. sPath = p.GetLocalPath(sPath, caseI)
  191. if sPath != "" {
  192. return p.osChmod(sPath, mode)
  193. }
  194. return nil
  195. }
  196. func (p *SSHPartition) osIsDir(path string) bool {
  197. _, err := p.term.Run(fmt.Sprintf("test -d %s", path))
  198. if err != nil {
  199. return false
  200. }
  201. return true
  202. }
  203. func (p *SSHPartition) osListDir(path string) ([]string, error) {
  204. if !p.osIsDir(path) {
  205. return nil, fmt.Errorf("Path %s is not dir", path)
  206. }
  207. ret, err := p.term.Run(fmt.Sprintf("ls -a %s", path))
  208. if err != nil {
  209. return nil, err
  210. }
  211. files := []string{}
  212. for _, f := range ret {
  213. f = strings.TrimSpace(f)
  214. if !utils.IsInStringArray(f, []string{"", ".", ".."}) {
  215. files = append(files, f)
  216. }
  217. }
  218. return files, nil
  219. }
  220. func (p *SSHPartition) GetLocalPath(sPath string, caseI bool) string {
  221. var fullPath = p.mountPath
  222. pathSegs := strings.Split(sPath, "/")
  223. for _, seg := range pathSegs {
  224. if len(seg) == 0 {
  225. continue
  226. }
  227. var realSeg string
  228. files, err := p.osListDir(fullPath)
  229. if err != nil {
  230. log.Errorf("List dir %s error: %v", sPath, err)
  231. return ""
  232. }
  233. for _, f := range files {
  234. if f == seg || (caseI && (strings.ToLower(f)) == strings.ToLower(seg)) {
  235. realSeg = f
  236. break
  237. }
  238. }
  239. if len(realSeg) > 0 {
  240. fullPath = path.Join(fullPath, realSeg)
  241. } else {
  242. return ""
  243. }
  244. }
  245. return fullPath
  246. }
  247. func (f *SSHPartition) Symlink(src string, dst string, caseInsensitive bool) error {
  248. odstDir := path.Dir(dst)
  249. if err := f.Mkdir(odstDir, 0755, caseInsensitive); err != nil {
  250. return errors.Wrapf(err, "Mkdir %s", odstDir)
  251. }
  252. if f.Exists(dst, caseInsensitive) {
  253. f.Remove(dst, caseInsensitive)
  254. }
  255. odstDir = f.GetLocalPath(odstDir, caseInsensitive)
  256. dst = path.Join(odstDir, path.Base(dst))
  257. return f.osSymlink(src, dst)
  258. }
  259. func (p *SSHPartition) Exists(sPath string, caseInsensitive bool) bool {
  260. sPath = p.GetLocalPath(sPath, caseInsensitive)
  261. if len(sPath) > 0 {
  262. return p.osPathExists(sPath)
  263. }
  264. return false
  265. }
  266. func (p *SSHPartition) sshFileGetContents(path string) ([]byte, error) {
  267. cmd := fmt.Sprintf("cat %s", path)
  268. ret, err := p.term.Run(cmd)
  269. if err != nil {
  270. return nil, err
  271. }
  272. if len(ret) > 0 && ret[len(ret)-1] == "" {
  273. ret = ret[0 : len(ret)-1]
  274. }
  275. retBytes := []byte(strings.Join(ret, "\n"))
  276. return retBytes, nil
  277. }
  278. func (p *SSHPartition) FileGetContents(sPath string, caseInsensitive bool) ([]byte, error) {
  279. sPath = p.GetLocalPath(sPath, caseInsensitive)
  280. return p.FileGetContentsByPath(sPath)
  281. }
  282. func (p *SSHPartition) FileGetContentsByPath(sPath string) ([]byte, error) {
  283. if len(sPath) == 0 {
  284. return nil, fmt.Errorf("Path is not provide")
  285. }
  286. return p.sshFileGetContents(sPath)
  287. }
  288. func (p *SSHPartition) sshFilePutContents(sPath, content string, modAppend bool) error {
  289. op := ">"
  290. if modAppend {
  291. op = ">>"
  292. }
  293. cmds := []string{}
  294. if len(content) == 0 {
  295. cmd := fmt.Sprintf(`echo -n -e "" %s %s`, op, sPath)
  296. cmds = append(cmds, cmd)
  297. } else {
  298. var chunkSize int = 8192
  299. for offset := 0; offset < len(content); offset += chunkSize {
  300. end := offset + chunkSize
  301. if end > len(content) {
  302. end = len(content)
  303. }
  304. ll, err := stringutils.EscapeEchoString(content[offset:end])
  305. if err != nil {
  306. return fmt.Errorf("EscapeEchoString %q error: %v", content[offset:end], err)
  307. }
  308. cmd := fmt.Sprintf(`echo -n -e "%s" %s %s`, ll, op, sPath)
  309. cmds = append(cmds, cmd)
  310. if op == ">" {
  311. op = ">>"
  312. }
  313. }
  314. }
  315. _, err := p.term.Run(cmds...)
  316. return err
  317. }
  318. func (p *SSHPartition) FilePutContents(sPath, content string, modAppend, caseInsensitive bool) error {
  319. sFilePath := p.GetLocalPath(sPath, caseInsensitive)
  320. if len(sFilePath) > 0 {
  321. sPath = sFilePath
  322. } else {
  323. dirPath := p.GetLocalPath(path.Dir(sPath), caseInsensitive)
  324. if len(dirPath) > 0 {
  325. sPath = path.Join(dirPath, path.Base(sPath))
  326. }
  327. }
  328. if len(sPath) > 0 {
  329. return p.sshFilePutContents(sPath, content, modAppend)
  330. }
  331. return fmt.Errorf("Can't put content to %s", sPath)
  332. }
  333. func (p *SSHPartition) ListDir(sPath string, caseInsensitive bool) []string {
  334. sPath = p.GetLocalPath(sPath, caseInsensitive)
  335. if len(sPath) > 0 {
  336. ret, err := p.osListDir(sPath)
  337. if err != nil {
  338. log.Errorf("list dir for %s: %v", sPath, err)
  339. return nil
  340. }
  341. return ret
  342. }
  343. return nil
  344. }
  345. func (p *SSHPartition) osChown(sPath string, uid, gid int) error {
  346. cmd := fmt.Sprintf("chown %d:%d %s", uid, gid, sPath)
  347. _, err := p.term.Run(cmd)
  348. return err
  349. }
  350. func (p *SSHPartition) Chown(sPath string, uid, gid int, caseInsensitive bool) error {
  351. sPath = p.GetLocalPath(sPath, caseInsensitive)
  352. if len(sPath) == 0 {
  353. return fmt.Errorf("Can't get local path: %s", sPath)
  354. }
  355. return p.osChown(sPath, uid, gid)
  356. }
  357. func (p *SSHPartition) osRemove(sPath string) error {
  358. cmd := fmt.Sprintf("rm %s", sPath)
  359. _, err := p.term.Run(cmd)
  360. return err
  361. }
  362. func (p *SSHPartition) Remove(sPath string, caseInsensitive bool) {
  363. sPath = p.GetLocalPath(sPath, caseInsensitive)
  364. if len(sPath) > 0 {
  365. p.osRemove(sPath)
  366. }
  367. }
  368. func (p *SSHPartition) CheckOrAddUser(user, homeDir string, isSys bool) (realHomeDir string, err error) {
  369. var exist bool
  370. if exist, realHomeDir, err = p.checkUser(user); err != nil || exist {
  371. if exist {
  372. cmd := []string{"chage", "-R", p.mountPath, "-E", "-1", "-m", "0", "-M", "99999", "-I", "-1", user}
  373. _, err = p.term.Run(strings.Join(cmd, " "))
  374. if err != nil {
  375. if !strings.Contains(err.Error(), "not found") {
  376. err = errors.Wrap(err, "chage")
  377. return
  378. } else {
  379. err = nil
  380. }
  381. }
  382. if !p.Exists(realHomeDir, false) {
  383. err = p.Mkdir(realHomeDir, 0700, false)
  384. if err != nil {
  385. err = errors.Wrapf(err, "Mkdir %s", realHomeDir)
  386. } else {
  387. cmd := []string{"/usr/sbin/chroot", p.mountPath, "chown", user, realHomeDir}
  388. _, err = p.term.Run(strings.Join(cmd, " "))
  389. if err != nil {
  390. err = errors.Wrap(err, "chown")
  391. }
  392. }
  393. }
  394. }
  395. return
  396. }
  397. return path.Join(homeDir, user), p.userAdd(user, homeDir, isSys)
  398. }
  399. func (p *SSHPartition) checkUser(user string) (exist bool, homeDir string, err error) {
  400. cmd := fmt.Sprintf("/usr/sbin/chroot %s /bin/cat /etc/passwd", p.mountPath)
  401. lines, err := p.term.Run(cmd)
  402. if err != nil {
  403. return
  404. }
  405. log.Debugf("exec command 'cat /etc/passwd', output: %v", lines)
  406. for i := len(lines) - 1; i >= 0; i-- {
  407. userInfos := strings.Split(strings.TrimSpace(lines[i]), ":")
  408. if len(userInfos) < 6 {
  409. continue
  410. }
  411. if userInfos[0] != user {
  412. continue
  413. }
  414. exist = true
  415. homeDir = userInfos[5]
  416. break
  417. }
  418. return
  419. }
  420. func (p *SSHPartition) userAdd(user, homeDir string, isSys bool) error {
  421. cmd := fmt.Sprintf("/usr/sbin/chroot %s /usr/sbin/useradd -m -s /bin/bash %s", p.mountPath, user)
  422. if isSys {
  423. cmd += " -r -e '' -f '-1' -K 'PASS_MAX_DAYS=-1'"
  424. }
  425. if len(homeDir) > 0 {
  426. cmd += fmt.Sprintf(" -d %s", path.Join(homeDir, user))
  427. }
  428. _, err := p.term.Run(cmd)
  429. return err
  430. }
  431. func (p *SSHPartition) Passwd(user, password string, caseInsensitive bool) error {
  432. newpass := "/tmp/newpass"
  433. p.sshFilePutContents(newpass, fmt.Sprintf("%s\n%s\n", password, password), false)
  434. cmd := fmt.Sprintf("/usr/sbin/chroot %s /usr/bin/passwd %s < %s", p.mountPath, user, newpass)
  435. _, err := p.term.Run(cmd)
  436. return err
  437. }
  438. func (p *SSHPartition) osStat(sPath string) (os.FileInfo, error) {
  439. cmd := fmt.Sprintf("ls -a -l -n -i -s -d %s", sPath)
  440. ret, err := p.term.Run(cmd)
  441. if err != nil {
  442. return nil, err
  443. }
  444. for _, line := range ret {
  445. dat := regexp.MustCompile(`\s+`).Split(strings.TrimSpace(line), -1)
  446. if len(dat) > 7 && ((dat[2][0] != 'l' && dat[len(dat)-1] == sPath) ||
  447. (dat[2][0] == 'l' && dat[len(dat)-3] == sPath)) {
  448. stMode, err := fsdriver.ModeStr2Bin(dat[2])
  449. if err != nil {
  450. return nil, err
  451. }
  452. stIno, _ := strconv.Atoi(dat[0])
  453. stUid, _ := strconv.Atoi(dat[4])
  454. stGid, _ := strconv.Atoi(dat[5])
  455. stSize, _ := strconv.Atoi(dat[6])
  456. info := fsdriver.NewFileInfo(
  457. sPath,
  458. int64(stSize),
  459. os.FileMode(stMode),
  460. dat[2][0] == 'd',
  461. &syscall.Stat_t{
  462. Ino: uint64(stIno),
  463. Uid: uint32(stUid),
  464. Gid: uint32(stGid),
  465. Size: int64(stSize),
  466. },
  467. )
  468. return info, nil
  469. }
  470. }
  471. return nil, fmt.Errorf("Can't stat for path %s", sPath)
  472. }
  473. func (p *SSHPartition) Stat(sPath string, caseInsensitive bool) os.FileInfo {
  474. sPath = p.GetLocalPath(sPath, caseInsensitive)
  475. if len(sPath) == 0 {
  476. return nil
  477. }
  478. info, err := p.osStat(sPath)
  479. if err != nil {
  480. log.Errorf("stat %s error: %v", sPath, err)
  481. return nil
  482. }
  483. return info
  484. }
  485. func (p *SSHPartition) Zerofiles(dir string, caseI bool) error {
  486. return nil
  487. }
  488. func (p *SSHPartition) GetReadonly() bool {
  489. return false
  490. }
  491. func (p *SSHPartition) SupportSerialPorts() bool {
  492. return true
  493. }
  494. func (p *SSHPartition) Cleandir(dir string, keepdir, caseInsensitive bool) error {
  495. return nil
  496. }
  497. func (p *SSHPartition) Zerofree() {
  498. log.Warningf("zerofree should not called in ssh partition")
  499. }
  500. func MountSSHRootfs(tool *disktool.SSHPartitionTool, term *ssh.Client, layouts []baremetal.Layout) (*SSHPartition, fsdriver.IRootFsDriver, error) {
  501. // tool, err := disktool.NewSSHPartitionTool(term, layouts)
  502. // if err != nil {
  503. // return nil, nil, err
  504. // }
  505. // tool.RetrievePartitionInfo()
  506. rootDisk := tool.GetRootDisk()
  507. if err := rootDisk.ReInitInfo(); err != nil {
  508. return nil, nil, errors.Wrapf(err, "Reinit root disk")
  509. }
  510. parts := rootDisk.GetPartitions()
  511. if len(parts) == 0 {
  512. return nil, nil, fmt.Errorf("Not found root disk partitions")
  513. }
  514. for _, part := range parts {
  515. dev := NewSSHPartition(term, part.GetDev(), false)
  516. if !dev.Mount() {
  517. continue
  518. }
  519. rootFs, err := guestfs.DetectRootFs(dev)
  520. if err == nil {
  521. log.Infof("Use class %#v", rootFs)
  522. return dev, rootFs, nil
  523. }
  524. dev.Umount()
  525. }
  526. return nil, nil, fmt.Errorf("Fail to find rootfs")
  527. }
  528. func (p *SSHPartition) GenerateSshHostKeys() error {
  529. for _, cmd := range []string{
  530. "touch /dev/null",
  531. "/usr/bin/ssh-keygen -A",
  532. } {
  533. _, err := p.term.Run(fmt.Sprintf("/usr/sbin/chroot %s %s", p.mountPath, cmd))
  534. if err != nil {
  535. return errors.Wrapf(err, "GenerateSshHostKeys exec %s", cmd)
  536. }
  537. }
  538. return nil
  539. }