// Copyright 2019 Yunion // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package kvmpart import ( "fmt" "io/ioutil" "os" "os/exec" "path" "strings" "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/onecloud/pkg/util/fileutils2" "yunion.io/x/onecloud/pkg/util/procutils" ) type SLocalGuestFS struct { mountPath string } func NewLocalGuestFS(mountPath string) *SLocalGuestFS { var ret = new(SLocalGuestFS) ret.mountPath = mountPath return ret } func (f *SLocalGuestFS) GetMountPath() string { return f.mountPath } func (f *SLocalGuestFS) SupportSerialPorts() bool { return false } func (f *SLocalGuestFS) GetLocalPath(sPath string, caseInsensitive bool) string { if sPath == "." { sPath = "" } var fullPath = f.mountPath pathSegs := strings.Split(sPath, "/") for _, seg := range pathSegs { if len(seg) > 0 { var realSeg string files, _ := ioutil.ReadDir(fullPath) for _, file := range files { var f = file.Name() if f == seg || (caseInsensitive && strings.ToLower(f) == strings.ToLower(seg)) || (seg[len(seg)-1] == '*' && (strings.HasPrefix(f, seg[:len(seg)-1]) || (caseInsensitive && strings.HasPrefix(strings.ToLower(f), strings.ToLower(seg[:len(seg)-1]))))) { realSeg = f break } } if len(realSeg) > 0 { fullPath = path.Join(fullPath, realSeg) } else { return "" } } } return fullPath } func (f *SLocalGuestFS) Remove(path string, caseInsensitive bool) { path = f.GetLocalPath(path, caseInsensitive) if len(path) > 0 { os.Remove(path) } } func (f *SLocalGuestFS) Mkdir(sPath string, mode int, caseInsensitive bool) error { segs := strings.Split(sPath, "/") sPath = "" pPath := f.GetLocalPath("/", caseInsensitive) for _, s := range segs { if len(s) > 0 { sPath = path.Join(sPath, s) vPath := f.GetLocalPath(sPath, caseInsensitive) if len(vPath) == 0 { if err := os.Mkdir(path.Join(pPath, s), os.FileMode(mode)); err != nil { return err } pPath = f.GetLocalPath(sPath, caseInsensitive) } else { pPath = vPath } } } return nil } func (f *SLocalGuestFS) ListDir(sPath string, caseInsensitive bool) []string { sPath = f.GetLocalPath(sPath, caseInsensitive) if len(sPath) > 0 { files, err := ioutil.ReadDir(sPath) if err != nil { log.Errorln(err) return nil } var res = make([]string, 0) for _, file := range files { res = append(res, file.Name()) } return res } return nil } func (f *SLocalGuestFS) Cleandir(dir string, keepdir, caseInsensitive bool) error { sPath := f.GetLocalPath(dir, caseInsensitive) if len(sPath) > 0 { return fileutils2.Cleandir(sPath, keepdir) } return fmt.Errorf("No such file %s", sPath) } func (f *SLocalGuestFS) Zerofiles(dir string, caseInsensitive bool) error { sPath := f.GetLocalPath(dir, caseInsensitive) if len(sPath) > 0 { return fileutils2.Zerofiles(sPath) } return fmt.Errorf("No such file %s", sPath) } func (f *SLocalGuestFS) Passwd(account, password string, caseInsensitive bool) error { var proc = exec.Command("chroot", f.mountPath, "passwd", account) passwordInput := fmt.Sprintf("%s\n%s\n", password, password) proc.Stdin = strings.NewReader(passwordInput) out, err := proc.CombinedOutput() if err != nil { return errors.Wrapf(err, "failed change passwd %s", out) } log.Infof("Passwd %s", out) return nil } func (f *SLocalGuestFS) Stat(usrDir string, caseInsensitive bool) os.FileInfo { sPath := f.GetLocalPath(usrDir, caseInsensitive) if len(sPath) > 0 { fileInfo, err := os.Stat(sPath) if err != nil { log.Errorln(err) } return fileInfo } return nil } func (f *SLocalGuestFS) Symlink(src string, dst string, caseInsensitive bool) error { dir := path.Dir(dst) if err := f.Mkdir(dir, 0755, caseInsensitive); err != nil { return errors.Wrapf(err, "Mkdir %s", dir) } if f.Exists(dst, caseInsensitive) { f.Remove(dst, caseInsensitive) } dir = f.GetLocalPath(dir, caseInsensitive) dst = path.Join(dir, path.Base(dst)) return os.Symlink(src, dst) } func (f *SLocalGuestFS) Exists(sPath string, caseInsensitive bool) bool { sPath = f.GetLocalPath(sPath, caseInsensitive) if len(sPath) > 0 { return fileutils2.Exists(sPath) } return false } func (f *SLocalGuestFS) Chown(sPath string, uid, gid int, caseInsensitive bool) error { sPath = f.GetLocalPath(sPath, caseInsensitive) if len(sPath) > 0 { return os.Chown(sPath, uid, gid) } return nil } func (f *SLocalGuestFS) Chmod(sPath string, mode uint32, caseInsensitive bool) error { sPath = f.GetLocalPath(sPath, caseInsensitive) if len(sPath) > 0 { return os.Chmod(sPath, os.FileMode(mode)) } return nil } func (f *SLocalGuestFS) updateUserEtcShadow(username string) error { sPath := f.GetLocalPath("/etc/shadow", false) if !fileutils2.Exists(sPath) { return nil } content, err := fileutils2.FileGetContents(sPath) if err != nil { return errors.Wrap(err, "read /etc/shadow") } var ( minimumDays = "0" // -m 0 maximumDays = "99999" // -M 99999 ) lines := strings.Split(string(content), "\n") for i, line := range lines { fields := strings.Split(line, ":") if len(fields) >= 7 && fields[0] == username { fields[3] = minimumDays fields[4] = maximumDays fields[5] = "" // password warning period fields[6] = "" // password inactivity period fields[7] = "" // account expiration date line = strings.Join(fields, ":") lines[i] = line break } } newContent := strings.Join(lines, "\n") err = fileutils2.FilePutContents(sPath, newContent, false) if err != nil { return errors.Wrapf(err, "read %s, put %s to /etc/shadow", content, newContent) } return nil } func (f *SLocalGuestFS) CheckOrAddUser(user, homeDir string, isSys bool) (realHomeDir string, err error) { var exist bool if exist, realHomeDir, err = f.checkUser(user); err != nil || exist { if exist { err = f.updateUserEtcShadow(user) if err != nil { err = errors.Wrap(err, "updateUserEtcShadow") return } if !f.Exists(realHomeDir, false) { err = f.Mkdir(realHomeDir, 0700, false) if err != nil { err = errors.Wrapf(err, "Mkdir %s", realHomeDir) } else { cmd := []string{"chroot", f.mountPath, "chown", user, realHomeDir} err = procutils.NewCommand(cmd[0], cmd[1:]...).Run() if err != nil { err = errors.Wrap(err, "chown") } } } } return } return path.Join(homeDir, user), f.userAdd(user, homeDir, isSys) } func (f *SLocalGuestFS) checkUser(user string) (exist bool, homeDir string, err error) { cmd := []string{"chroot", f.mountPath, "cat", "/etc/passwd"} command := procutils.NewCommand(cmd[0], cmd[1:]...) output, err := command.Output() if err != nil { return } lines := strings.Split(strings.TrimSpace(string(output)), "\n") for i := len(lines) - 1; i >= 0; i-- { userInfos := strings.Split(strings.TrimSpace(lines[i]), ":") if len(userInfos) < 6 { continue } if userInfos[0] != user { continue } exist = true homeDir = userInfos[5] break } return } func (f *SLocalGuestFS) userAdd(user, homeDir string, isSys bool) error { if err := f.Mkdir(homeDir, 0755, false); err != nil { return errors.Wrap(err, "Mkdir") } cmd := []string{"chroot", f.mountPath, "useradd", "-m", "-s", "/bin/bash", user} if isSys { cmd = append(cmd, "-r", "-e", "", "-f", "-1", "-K", "PASS_MAX_DAYS=-1") } if len(homeDir) > 0 { cmd = append(cmd, "-d", path.Join(homeDir, user)) } output, err := procutils.NewCommand(cmd[0], cmd[1:]...).Output() if err != nil { log.Errorf("Useradd fail: %s, %s", err, output) return fmt.Errorf("%s", output) } else { log.Infof("Useradd: %s", output) } return nil } func (f *SLocalGuestFS) FileGetContents(sPath string, caseInsensitive bool) ([]byte, error) { sPath = f.GetLocalPath(sPath, caseInsensitive) return f.FileGetContentsByPath(sPath) } func (f *SLocalGuestFS) FileGetContentsByPath(sPath string) ([]byte, error) { if len(sPath) > 0 { return ioutil.ReadFile(sPath) } return nil, fmt.Errorf("Cann't find local path") } func (f *SLocalGuestFS) FilePutContents(sPath, content string, modAppend, caseInsensitive bool) error { sFilePath := f.GetLocalPath(sPath, caseInsensitive) if len(sFilePath) > 0 { sPath = sFilePath } else { dirPath := f.GetLocalPath(path.Dir(sPath), caseInsensitive) if len(dirPath) > 0 { sPath = path.Join(dirPath, path.Base(sPath)) } } if len(sPath) > 0 { return fileutils2.FilePutContents(sPath, content, modAppend) } else { return fmt.Errorf("Can't put content to empty Path") } } func (f *SLocalGuestFS) GenerateSshHostKeys() error { for _, cmd := range [][]string{ {f.mountPath, "touch", "/dev/null"}, {f.mountPath, "/usr/bin/ssh-keygen", "-A"}, } { output, err := procutils.NewCommand("chroot", cmd...).Output() if err != nil { return errors.Wrapf(err, "GenerateSshHostKeys %s %s", strings.Join(cmd, " "), output) } } return nil }