localfs.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  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. "fmt"
  17. "io/ioutil"
  18. "os"
  19. "os/exec"
  20. "path"
  21. "strings"
  22. "yunion.io/x/log"
  23. "yunion.io/x/pkg/errors"
  24. "yunion.io/x/onecloud/pkg/util/fileutils2"
  25. "yunion.io/x/onecloud/pkg/util/procutils"
  26. )
  27. type SLocalGuestFS struct {
  28. mountPath string
  29. }
  30. func NewLocalGuestFS(mountPath string) *SLocalGuestFS {
  31. var ret = new(SLocalGuestFS)
  32. ret.mountPath = mountPath
  33. return ret
  34. }
  35. func (f *SLocalGuestFS) GetMountPath() string {
  36. return f.mountPath
  37. }
  38. func (f *SLocalGuestFS) SupportSerialPorts() bool {
  39. return false
  40. }
  41. func (f *SLocalGuestFS) GetLocalPath(sPath string, caseInsensitive bool) string {
  42. if sPath == "." {
  43. sPath = ""
  44. }
  45. var fullPath = f.mountPath
  46. pathSegs := strings.Split(sPath, "/")
  47. for _, seg := range pathSegs {
  48. if len(seg) > 0 {
  49. var realSeg string
  50. files, _ := ioutil.ReadDir(fullPath)
  51. for _, file := range files {
  52. var f = file.Name()
  53. if f == seg || (caseInsensitive && strings.ToLower(f) == strings.ToLower(seg)) ||
  54. (seg[len(seg)-1] == '*' && (strings.HasPrefix(f, seg[:len(seg)-1]) ||
  55. (caseInsensitive && strings.HasPrefix(strings.ToLower(f),
  56. strings.ToLower(seg[:len(seg)-1]))))) {
  57. realSeg = f
  58. break
  59. }
  60. }
  61. if len(realSeg) > 0 {
  62. fullPath = path.Join(fullPath, realSeg)
  63. } else {
  64. return ""
  65. }
  66. }
  67. }
  68. return fullPath
  69. }
  70. func (f *SLocalGuestFS) Remove(path string, caseInsensitive bool) {
  71. path = f.GetLocalPath(path, caseInsensitive)
  72. if len(path) > 0 {
  73. os.Remove(path)
  74. }
  75. }
  76. func (f *SLocalGuestFS) Mkdir(sPath string, mode int, caseInsensitive bool) error {
  77. segs := strings.Split(sPath, "/")
  78. sPath = ""
  79. pPath := f.GetLocalPath("/", caseInsensitive)
  80. for _, s := range segs {
  81. if len(s) > 0 {
  82. sPath = path.Join(sPath, s)
  83. vPath := f.GetLocalPath(sPath, caseInsensitive)
  84. if len(vPath) == 0 {
  85. if err := os.Mkdir(path.Join(pPath, s), os.FileMode(mode)); err != nil {
  86. return err
  87. }
  88. pPath = f.GetLocalPath(sPath, caseInsensitive)
  89. } else {
  90. pPath = vPath
  91. }
  92. }
  93. }
  94. return nil
  95. }
  96. func (f *SLocalGuestFS) ListDir(sPath string, caseInsensitive bool) []string {
  97. sPath = f.GetLocalPath(sPath, caseInsensitive)
  98. if len(sPath) > 0 {
  99. files, err := ioutil.ReadDir(sPath)
  100. if err != nil {
  101. log.Errorln(err)
  102. return nil
  103. }
  104. var res = make([]string, 0)
  105. for _, file := range files {
  106. res = append(res, file.Name())
  107. }
  108. return res
  109. }
  110. return nil
  111. }
  112. func (f *SLocalGuestFS) Cleandir(dir string, keepdir, caseInsensitive bool) error {
  113. sPath := f.GetLocalPath(dir, caseInsensitive)
  114. if len(sPath) > 0 {
  115. return fileutils2.Cleandir(sPath, keepdir)
  116. }
  117. return fmt.Errorf("No such file %s", sPath)
  118. }
  119. func (f *SLocalGuestFS) Zerofiles(dir string, caseInsensitive bool) error {
  120. sPath := f.GetLocalPath(dir, caseInsensitive)
  121. if len(sPath) > 0 {
  122. return fileutils2.Zerofiles(sPath)
  123. }
  124. return fmt.Errorf("No such file %s", sPath)
  125. }
  126. func (f *SLocalGuestFS) Passwd(account, password string, caseInsensitive bool) error {
  127. var proc = exec.Command("chroot", f.mountPath, "passwd", account)
  128. passwordInput := fmt.Sprintf("%s\n%s\n", password, password)
  129. proc.Stdin = strings.NewReader(passwordInput)
  130. out, err := proc.CombinedOutput()
  131. if err != nil {
  132. return errors.Wrapf(err, "failed change passwd %s", out)
  133. }
  134. log.Infof("Passwd %s", out)
  135. return nil
  136. }
  137. func (f *SLocalGuestFS) Stat(usrDir string, caseInsensitive bool) os.FileInfo {
  138. sPath := f.GetLocalPath(usrDir, caseInsensitive)
  139. if len(sPath) > 0 {
  140. fileInfo, err := os.Stat(sPath)
  141. if err != nil {
  142. log.Errorln(err)
  143. }
  144. return fileInfo
  145. }
  146. return nil
  147. }
  148. func (f *SLocalGuestFS) Symlink(src string, dst string, caseInsensitive bool) error {
  149. dir := path.Dir(dst)
  150. if err := f.Mkdir(dir, 0755, caseInsensitive); err != nil {
  151. return errors.Wrapf(err, "Mkdir %s", dir)
  152. }
  153. if f.Exists(dst, caseInsensitive) {
  154. f.Remove(dst, caseInsensitive)
  155. }
  156. dir = f.GetLocalPath(dir, caseInsensitive)
  157. dst = path.Join(dir, path.Base(dst))
  158. return os.Symlink(src, dst)
  159. }
  160. func (f *SLocalGuestFS) Exists(sPath string, caseInsensitive bool) bool {
  161. sPath = f.GetLocalPath(sPath, caseInsensitive)
  162. if len(sPath) > 0 {
  163. return fileutils2.Exists(sPath)
  164. }
  165. return false
  166. }
  167. func (f *SLocalGuestFS) Chown(sPath string, uid, gid int, caseInsensitive bool) error {
  168. sPath = f.GetLocalPath(sPath, caseInsensitive)
  169. if len(sPath) > 0 {
  170. return os.Chown(sPath, uid, gid)
  171. }
  172. return nil
  173. }
  174. func (f *SLocalGuestFS) Chmod(sPath string, mode uint32, caseInsensitive bool) error {
  175. sPath = f.GetLocalPath(sPath, caseInsensitive)
  176. if len(sPath) > 0 {
  177. return os.Chmod(sPath, os.FileMode(mode))
  178. }
  179. return nil
  180. }
  181. func (f *SLocalGuestFS) updateUserEtcShadow(username string) error {
  182. sPath := f.GetLocalPath("/etc/shadow", false)
  183. if !fileutils2.Exists(sPath) {
  184. return nil
  185. }
  186. content, err := fileutils2.FileGetContents(sPath)
  187. if err != nil {
  188. return errors.Wrap(err, "read /etc/shadow")
  189. }
  190. var (
  191. minimumDays = "0" // -m 0
  192. maximumDays = "99999" // -M 99999
  193. )
  194. lines := strings.Split(string(content), "\n")
  195. for i, line := range lines {
  196. fields := strings.Split(line, ":")
  197. if len(fields) >= 7 && fields[0] == username {
  198. fields[3] = minimumDays
  199. fields[4] = maximumDays
  200. fields[5] = "" // password warning period
  201. fields[6] = "" // password inactivity period
  202. fields[7] = "" // account expiration date
  203. line = strings.Join(fields, ":")
  204. lines[i] = line
  205. break
  206. }
  207. }
  208. newContent := strings.Join(lines, "\n")
  209. err = fileutils2.FilePutContents(sPath, newContent, false)
  210. if err != nil {
  211. return errors.Wrapf(err, "read %s, put %s to /etc/shadow", content, newContent)
  212. }
  213. return nil
  214. }
  215. func (f *SLocalGuestFS) CheckOrAddUser(user, homeDir string, isSys bool) (realHomeDir string, err error) {
  216. var exist bool
  217. if exist, realHomeDir, err = f.checkUser(user); err != nil || exist {
  218. if exist {
  219. err = f.updateUserEtcShadow(user)
  220. if err != nil {
  221. err = errors.Wrap(err, "updateUserEtcShadow")
  222. return
  223. }
  224. if !f.Exists(realHomeDir, false) {
  225. err = f.Mkdir(realHomeDir, 0700, false)
  226. if err != nil {
  227. err = errors.Wrapf(err, "Mkdir %s", realHomeDir)
  228. } else {
  229. cmd := []string{"chroot", f.mountPath, "chown", user, realHomeDir}
  230. err = procutils.NewCommand(cmd[0], cmd[1:]...).Run()
  231. if err != nil {
  232. err = errors.Wrap(err, "chown")
  233. }
  234. }
  235. }
  236. }
  237. return
  238. }
  239. return path.Join(homeDir, user), f.userAdd(user, homeDir, isSys)
  240. }
  241. func (f *SLocalGuestFS) checkUser(user string) (exist bool, homeDir string, err error) {
  242. cmd := []string{"chroot", f.mountPath, "cat", "/etc/passwd"}
  243. command := procutils.NewCommand(cmd[0], cmd[1:]...)
  244. output, err := command.Output()
  245. if err != nil {
  246. return
  247. }
  248. lines := strings.Split(strings.TrimSpace(string(output)), "\n")
  249. for i := len(lines) - 1; i >= 0; i-- {
  250. userInfos := strings.Split(strings.TrimSpace(lines[i]), ":")
  251. if len(userInfos) < 6 {
  252. continue
  253. }
  254. if userInfos[0] != user {
  255. continue
  256. }
  257. exist = true
  258. homeDir = userInfos[5]
  259. break
  260. }
  261. return
  262. }
  263. func (f *SLocalGuestFS) userAdd(user, homeDir string, isSys bool) error {
  264. if err := f.Mkdir(homeDir, 0755, false); err != nil {
  265. return errors.Wrap(err, "Mkdir")
  266. }
  267. cmd := []string{"chroot", f.mountPath, "useradd", "-m", "-s", "/bin/bash", user}
  268. if isSys {
  269. cmd = append(cmd, "-r", "-e", "", "-f", "-1", "-K", "PASS_MAX_DAYS=-1")
  270. }
  271. if len(homeDir) > 0 {
  272. cmd = append(cmd, "-d", path.Join(homeDir, user))
  273. }
  274. output, err := procutils.NewCommand(cmd[0], cmd[1:]...).Output()
  275. if err != nil {
  276. log.Errorf("Useradd fail: %s, %s", err, output)
  277. return fmt.Errorf("%s", output)
  278. } else {
  279. log.Infof("Useradd: %s", output)
  280. }
  281. return nil
  282. }
  283. func (f *SLocalGuestFS) FileGetContents(sPath string, caseInsensitive bool) ([]byte, error) {
  284. sPath = f.GetLocalPath(sPath, caseInsensitive)
  285. return f.FileGetContentsByPath(sPath)
  286. }
  287. func (f *SLocalGuestFS) FileGetContentsByPath(sPath string) ([]byte, error) {
  288. if len(sPath) > 0 {
  289. return ioutil.ReadFile(sPath)
  290. }
  291. return nil, fmt.Errorf("Cann't find local path")
  292. }
  293. func (f *SLocalGuestFS) FilePutContents(sPath, content string, modAppend, caseInsensitive bool) error {
  294. sFilePath := f.GetLocalPath(sPath, caseInsensitive)
  295. if len(sFilePath) > 0 {
  296. sPath = sFilePath
  297. } else {
  298. dirPath := f.GetLocalPath(path.Dir(sPath), caseInsensitive)
  299. if len(dirPath) > 0 {
  300. sPath = path.Join(dirPath, path.Base(sPath))
  301. }
  302. }
  303. if len(sPath) > 0 {
  304. return fileutils2.FilePutContents(sPath, content, modAppend)
  305. } else {
  306. return fmt.Errorf("Can't put content to empty Path")
  307. }
  308. }
  309. func (f *SLocalGuestFS) GenerateSshHostKeys() error {
  310. for _, cmd := range [][]string{
  311. {f.mountPath, "touch", "/dev/null"},
  312. {f.mountPath, "/usr/bin/ssh-keygen", "-A"},
  313. } {
  314. output, err := procutils.NewCommand("chroot", cmd...).Output()
  315. if err != nil {
  316. return errors.Wrapf(err, "GenerateSshHostKeys %s %s", strings.Join(cmd, " "), output)
  317. }
  318. }
  319. return nil
  320. }