created syncuids.go with code from utils.go
This commit is contained in:
committed by
Christian Goll
parent
61eae386af
commit
7f1636c9a9
263
internal/pkg/container/syncuids.go
Normal file
263
internal/pkg/container/syncuids.go
Normal file
@@ -0,0 +1,263 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/hpcng/warewulf/internal/pkg/util"
|
||||
"github.com/hpcng/warewulf/internal/pkg/wwlog"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type completeUserInfo struct {
|
||||
Name string
|
||||
UidHost int `access:"r,w"`
|
||||
GidHost int `access:"r,w"`
|
||||
UidCont int `access:"r,w"`
|
||||
GidCont int `access:"r,w"`
|
||||
FileListUid []string `access:"r,w"`
|
||||
FileListGid []string `access:"r,w"`
|
||||
}
|
||||
|
||||
type simpleUserInfo struct {
|
||||
name string
|
||||
uid int
|
||||
gid int
|
||||
}
|
||||
|
||||
/*
|
||||
sync the uids,gids from the host to the container
|
||||
*/
|
||||
func SyncUids(containerName string, showOnly bool) error {
|
||||
var userDb []completeUserInfo
|
||||
passwdName := "/etc/passwd"
|
||||
groupName := "/etc/group"
|
||||
fullPath := RootFsDir(containerName)
|
||||
hostName, err := createPasswdMap(passwdName)
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "Could not open "+passwdName)
|
||||
return err
|
||||
}
|
||||
// populate db with the user of the
|
||||
for _, user := range hostName {
|
||||
userDb = append(userDb, completeUserInfo{Name: user.name,
|
||||
UidHost: user.uid, GidHost: user.gid, UidCont: -1, GidCont: -1})
|
||||
}
|
||||
|
||||
contName, err := createPasswdMap(path.Join(fullPath, passwdName))
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "Could not open "+path.Join(fullPath, passwdName))
|
||||
return err
|
||||
}
|
||||
var userOnlyCont []string
|
||||
for _, userCont := range contName {
|
||||
foundUser := false
|
||||
for idxHost, userHost := range userDb {
|
||||
if userCont.name == userHost.Name {
|
||||
foundUser = true
|
||||
(&userDb[idxHost]).UidCont = userCont.uid
|
||||
(&userDb[idxHost]).GidCont = userCont.gid
|
||||
}
|
||||
}
|
||||
if !foundUser {
|
||||
userDb = append(userDb, completeUserInfo{Name: userCont.name,
|
||||
UidHost: -1, GidHost: -1, UidCont: userCont.uid, GidCont: userCont.gid})
|
||||
wwlog.Printf(wwlog.WARN, "user: %s:%v:%v not present on host\n", userCont.name, userCont.uid, userCont.gid)
|
||||
userOnlyCont = append(userOnlyCont, userCont.name)
|
||||
}
|
||||
|
||||
}
|
||||
// find out which user/group are only in the container
|
||||
for _, user := range userDb {
|
||||
if user.UidHost == -1 {
|
||||
for _, userCheck := range userDb {
|
||||
if userCheck.UidHost == user.UidCont {
|
||||
wwlog.Printf(wwlog.WARN, fmt.Sprintf("uid(%v) collision for host: %s and container: %s\n",
|
||||
user.UidCont, user.Name, userCheck.Name))
|
||||
return errors.New(fmt.Sprintf("user %s only present in container has same uid(%v) as user %s on host,\n"+
|
||||
"add this user to /etc/passwd on host", user.Name, user.UidCont, userCheck.Name))
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Users can have same gid, disabling this code
|
||||
if user.GidHost == -1 {
|
||||
for _, userCheck := range userDb {
|
||||
if userCheck.GidHost == user.GidCont {
|
||||
wwlog.Printf(wwlog.WARN, fmt.Sprintf("gid(%v) collision for host: %s and container: %s\n",
|
||||
user.GidCont, user.Name, userCheck.Name))
|
||||
return errors.New(fmt.Sprintf("user %s only present in container has same gid(%v) as user %s on host,\n"+
|
||||
" add this group to /etc/group on host", user.Name, user.GidCont, userCheck.Name))
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
if !showOnly {
|
||||
return nil
|
||||
}
|
||||
// create list of files which need changed ownerships in order to change them later what
|
||||
// avoid uid/gid collisions
|
||||
for idx, user := range userDb {
|
||||
if (user.UidHost != user.UidCont && user.UidHost != -1) ||
|
||||
(user.GidHost != user.GidCont && user.GidHost != -1 && user.UidHost != -1) {
|
||||
wwlog.Printf(wwlog.VERBOSE, fmt.Sprintf("host %s:%v:%v <-> container %s:%v:%v\n",
|
||||
user.Name, user.UidHost, user.GidHost, user.Name, user.UidCont, user.GidCont))
|
||||
err = filepath.Walk(fullPath, func(filePath string, info fs.FileInfo, err error) error {
|
||||
// root is always good, if we fail to get UID/GID of a file
|
||||
var uid, gid int
|
||||
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
|
||||
uid = int(stat.Uid)
|
||||
gid = int(stat.Gid)
|
||||
}
|
||||
if uid == user.UidCont {
|
||||
(&userDb[idx]).FileListUid = append((&userDb[idx]).FileListUid, filePath)
|
||||
}
|
||||
if gid == user.GidCont {
|
||||
(&userDb[idx]).FileListGid = append((&userDb[idx]).FileListGid, filePath)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
// change uids and gid of file
|
||||
for _, user := range userDb {
|
||||
if len(user.FileListUid) != 0 {
|
||||
//fmt.Printf("uidList(%s): %v\n", user.Name, user.FileListUid)
|
||||
for _, file := range user.FileListUid {
|
||||
fsInfo, err := os.Stat(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var gid int
|
||||
if stat, ok := fsInfo.Sys().(*syscall.Stat_t); ok {
|
||||
gid = int(stat.Gid)
|
||||
}
|
||||
wwlog.Printf(wwlog.DEBUG, "%s chown(%v,%v)\n", file, user.UidHost, gid)
|
||||
err = os.Chown(file, user.UidHost, gid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(user.FileListGid) != 0 {
|
||||
//fmt.Printf("gidList(%s): %v\n", user.Name, user.FileListGid)
|
||||
for _, file := range user.FileListGid {
|
||||
fsInfo, err := os.Stat(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var uid int
|
||||
if stat, ok := fsInfo.Sys().(*syscall.Stat_t); ok {
|
||||
uid = int(stat.Uid)
|
||||
}
|
||||
wwlog.Printf(wwlog.DEBUG, "%s chown(%v,%v)\n", file, user.UidHost, uid)
|
||||
// only chown files and dirs
|
||||
if fsInfo.IsDir() && fsInfo.Mode().IsRegular() {
|
||||
err = os.Chown(file, uid, user.GidHost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
// get the entries for the passwd/group file before copy over
|
||||
passwdEntries, err := getEntires(path.Join(fullPath, passwdName), userOnlyCont)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// implicitly assuming that users/groups which only exists on the host have the same name
|
||||
groupEntries, err := getEntires(path.Join(fullPath, groupName), userOnlyCont)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = os.Remove(path.Join(fullPath, passwdName)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = os.Remove(path.Join(fullPath, groupName)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = util.CopyFile(passwdName, path.Join(fullPath, passwdName)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = util.CopyFile(groupName, path.Join(fullPath, groupName)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = util.AppendLines(path.Join(fullPath, passwdName), passwdEntries); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = util.AppendLines(path.Join(fullPath, groupName), groupEntries); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
creates simple user db []simpleUserInfo for a /etc/{passwd|group} file
|
||||
*/
|
||||
func createPasswdMap(fileName string) ([]simpleUserInfo, error) {
|
||||
var nameDb []simpleUserInfo
|
||||
file, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
fileScanner := bufio.NewScanner(file)
|
||||
for fileScanner.Scan() {
|
||||
line := fileScanner.Text()
|
||||
entries := strings.Split(line, ":")
|
||||
name := entries[0]
|
||||
uid, err := strconv.Atoi(entries[2])
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.WARN, "could not parse uid(%s) for %s\n", entries[2], name)
|
||||
}
|
||||
gid, err := strconv.Atoi(entries[3])
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.WARN, "could not parse gid(%s) for %s\n", entries[2], name)
|
||||
}
|
||||
if name != "" {
|
||||
nameDb = append(nameDb, simpleUserInfo{name: name, uid: uid, gid: gid})
|
||||
|
||||
}
|
||||
}
|
||||
wwlog.Printf(wwlog.DEBUG, fmt.Sprintf("created uid/gid map with %v entries from %s\n", len(nameDb), fileName))
|
||||
return nameDb, nil
|
||||
}
|
||||
|
||||
/*
|
||||
Creates a slice with the entries of of passwd for the given slice of user names
|
||||
*/
|
||||
func getEntires(fileName string, names []string) ([]string, error) {
|
||||
file, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
var list []string
|
||||
fileScanner := bufio.NewScanner(file)
|
||||
for fileScanner.Scan() {
|
||||
line := fileScanner.Text()
|
||||
entries := strings.Split(line, ":")
|
||||
for _, name := range names {
|
||||
if entries[0] == name {
|
||||
list = append(list, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
wwlog.Printf(wwlog.DEBUG, "file: %s, list: %v\n", fileName, list)
|
||||
return list, nil
|
||||
}
|
||||
@@ -1,16 +1,8 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
@@ -78,269 +70,3 @@ func DeleteSource(name string) error {
|
||||
wwlog.Printf(wwlog.VERBOSE, "Removing path: %s\n", fullPath)
|
||||
return os.RemoveAll(fullPath)
|
||||
}
|
||||
|
||||
type completeUserInfo struct {
|
||||
Name string
|
||||
UidHost int `access:"r,w"`
|
||||
GidHost int `access:"r,w"`
|
||||
UidCont int `access:"r,w"`
|
||||
GidCont int `access:"r,w"`
|
||||
FileListUid []string `access:"r,w"`
|
||||
FileListGid []string `access:"r,w"`
|
||||
}
|
||||
|
||||
type simpleUserInfo struct {
|
||||
name string
|
||||
uid int
|
||||
gid int
|
||||
}
|
||||
|
||||
/*
|
||||
sync the uids,gids from the host to the container
|
||||
*/
|
||||
func SyncUids(containerName string, showOnly bool) error {
|
||||
var userDb []completeUserInfo
|
||||
passwdName := "/etc/passwd"
|
||||
groupName := "/etc/group"
|
||||
fullPath := RootFsDir(containerName)
|
||||
hostName, err := createPasswdMap(passwdName)
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "Could not open "+passwdName)
|
||||
return err
|
||||
}
|
||||
// populate db with the user of the
|
||||
for _, user := range hostName {
|
||||
userDb = append(userDb, completeUserInfo{Name: user.name,
|
||||
UidHost: user.uid, GidHost: user.gid, UidCont: -1, GidCont: -1})
|
||||
}
|
||||
|
||||
contName, err := createPasswdMap(path.Join(fullPath, passwdName))
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "Could not open "+path.Join(fullPath, passwdName))
|
||||
return err
|
||||
}
|
||||
var userOnlyCont []string
|
||||
for _, userCont := range contName {
|
||||
foundUser := false
|
||||
for idxHost, userHost := range userDb {
|
||||
if userCont.name == userHost.Name {
|
||||
foundUser = true
|
||||
(&userDb[idxHost]).UidCont = userCont.uid
|
||||
(&userDb[idxHost]).GidCont = userCont.gid
|
||||
}
|
||||
}
|
||||
if !foundUser {
|
||||
userDb = append(userDb, completeUserInfo{Name: userCont.name,
|
||||
UidHost: -1, GidHost: -1, UidCont: userCont.uid, GidCont: userCont.gid})
|
||||
wwlog.Printf(wwlog.WARN, "user: %s:%v:%v not present on host\n", userCont.name, userCont.uid, userCont.gid)
|
||||
userOnlyCont = append(userOnlyCont, userCont.name)
|
||||
}
|
||||
|
||||
}
|
||||
// find out which user/group are only in the container
|
||||
for _, user := range userDb {
|
||||
if user.UidHost == -1 {
|
||||
for _, userCheck := range userDb {
|
||||
if userCheck.UidHost == user.UidCont {
|
||||
wwlog.Printf(wwlog.WARN, fmt.Sprintf("uid(%v) collision for host: %s and container: %s\n",
|
||||
user.UidCont, user.Name, userCheck.Name))
|
||||
return errors.New(fmt.Sprintf("user %s only present in container has same uid(%v) as user %s on host,\n"+
|
||||
"add this user to /etc/passwd on host", user.Name, user.UidCont, userCheck.Name))
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Users can have same gid, disabling this code
|
||||
if user.GidHost == -1 {
|
||||
for _, userCheck := range userDb {
|
||||
if userCheck.GidHost == user.GidCont {
|
||||
wwlog.Printf(wwlog.WARN, fmt.Sprintf("gid(%v) collision for host: %s and container: %s\n",
|
||||
user.GidCont, user.Name, userCheck.Name))
|
||||
return errors.New(fmt.Sprintf("user %s only present in container has same gid(%v) as user %s on host,\n"+
|
||||
" add this group to /etc/group on host", user.Name, user.GidCont, userCheck.Name))
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
if !showOnly {
|
||||
return nil
|
||||
}
|
||||
// create list of files which need changed ownerships in order to change them later what
|
||||
// avoid uid/gid collisions
|
||||
for idx, user := range userDb {
|
||||
if (user.UidHost != user.UidCont && user.UidHost != -1) ||
|
||||
(user.GidHost != user.GidCont && user.GidHost != -1 && user.UidHost != -1) {
|
||||
wwlog.Printf(wwlog.VERBOSE, fmt.Sprintf("host %s:%v:%v <-> container %s:%v:%v\n",
|
||||
user.Name, user.UidHost, user.GidHost, user.Name, user.UidCont, user.GidCont))
|
||||
err = filepath.Walk(fullPath, func(filePath string, info fs.FileInfo, err error) error {
|
||||
// root is always good, if we fail to get UID/GID of a file
|
||||
var uid, gid int
|
||||
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
|
||||
uid = int(stat.Uid)
|
||||
gid = int(stat.Gid)
|
||||
}
|
||||
if uid == user.UidCont {
|
||||
(&userDb[idx]).FileListUid = append((&userDb[idx]).FileListUid, filePath)
|
||||
}
|
||||
if gid == user.GidCont {
|
||||
(&userDb[idx]).FileListGid = append((&userDb[idx]).FileListGid, filePath)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
// change uids and gid of file
|
||||
for _, user := range userDb {
|
||||
if len(user.FileListUid) != 0 {
|
||||
//fmt.Printf("uidList(%s): %v\n", user.Name, user.FileListUid)
|
||||
for _, file := range user.FileListUid {
|
||||
fsInfo, err := os.Stat(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var gid int
|
||||
if stat, ok := fsInfo.Sys().(*syscall.Stat_t); ok {
|
||||
gid = int(stat.Gid)
|
||||
}
|
||||
wwlog.Printf(wwlog.DEBUG, "%s chown(%v,%v)\n", file, user.UidHost, gid)
|
||||
err = os.Chown(file, user.UidHost, gid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(user.FileListGid) != 0 {
|
||||
//fmt.Printf("gidList(%s): %v\n", user.Name, user.FileListGid)
|
||||
for _, file := range user.FileListGid {
|
||||
fsInfo, err := os.Stat(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var uid int
|
||||
if stat, ok := fsInfo.Sys().(*syscall.Stat_t); ok {
|
||||
uid = int(stat.Uid)
|
||||
}
|
||||
wwlog.Printf(wwlog.DEBUG, "%s chown(%v,%v)\n", file, user.UidHost, uid)
|
||||
// only chown files and dirs
|
||||
if fsInfo.IsDir() && fsInfo.Mode().IsRegular() {
|
||||
err = os.Chown(file, uid, user.GidHost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
// get the entries for the passwd/group file before copy over
|
||||
passwdEntries, err := getEntires(path.Join(fullPath, passwdName), userOnlyCont)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// implicitly assuming that users/groups which only exists on the host have the same name
|
||||
groupEntries, err := getEntires(path.Join(fullPath, groupName), userOnlyCont)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = os.Remove(path.Join(fullPath, passwdName)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = os.Remove(path.Join(fullPath, groupName)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = util.CopyFile(passwdName, path.Join(fullPath, passwdName)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = util.CopyFile(groupName, path.Join(fullPath, groupName)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = appendLines(path.Join(fullPath, passwdName), passwdEntries); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = appendLines(path.Join(fullPath, groupName), groupEntries); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
creates simple user db []simpleUserInfo for a /etc/{passwd|group} file
|
||||
*/
|
||||
func createPasswdMap(fileName string) ([]simpleUserInfo, error) {
|
||||
var nameDb []simpleUserInfo
|
||||
file, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
fileScanner := bufio.NewScanner(file)
|
||||
for fileScanner.Scan() {
|
||||
line := fileScanner.Text()
|
||||
entries := strings.Split(line, ":")
|
||||
name := entries[0]
|
||||
uid, err := strconv.Atoi(entries[2])
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.WARN, "could not parse uid(%s) for %s\n", entries[2], name)
|
||||
}
|
||||
gid, err := strconv.Atoi(entries[3])
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.WARN, "could not parse gid(%s) for %s\n", entries[2], name)
|
||||
}
|
||||
if name != "" {
|
||||
nameDb = append(nameDb, simpleUserInfo{name: name, uid: uid, gid: gid})
|
||||
|
||||
}
|
||||
}
|
||||
wwlog.Printf(wwlog.DEBUG, fmt.Sprintf("created uid/gid map with %v entries from %s\n", len(nameDb), fileName))
|
||||
return nameDb, nil
|
||||
}
|
||||
|
||||
/*
|
||||
Creates a slice with the entries of of passwd for the given slice of user names
|
||||
*/
|
||||
func getEntires(fileName string, names []string) ([]string, error) {
|
||||
file, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
var list []string
|
||||
fileScanner := bufio.NewScanner(file)
|
||||
for fileScanner.Scan() {
|
||||
line := fileScanner.Text()
|
||||
entries := strings.Split(line, ":")
|
||||
for _, name := range names {
|
||||
if entries[0] == name {
|
||||
list = append(list, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
wwlog.Printf(wwlog.DEBUG, "file: %s, list: %v\n", fileName, list)
|
||||
return list, nil
|
||||
}
|
||||
|
||||
/*
|
||||
Appending the lines to the given file
|
||||
*/
|
||||
func appendLines(fileName string, lines []string) error {
|
||||
wwlog.Printf(wwlog.VERBOSE, "appending %v lines to %s\n", len(lines), fileName)
|
||||
file, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return errors.Errorf("Can't open file %s: %s", fileName, err)
|
||||
}
|
||||
defer file.Close()
|
||||
for _, line := range lines {
|
||||
wwlog.Printf(wwlog.DEBUG, "Appending '%s' to %s\n", line, fileName)
|
||||
if _, err := file.WriteString(fmt.Sprintf("%s\n", line)); err != nil {
|
||||
return errors.Errorf("Can't write to file %s: %s", fileName, err)
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -325,3 +325,23 @@ func IncrementIPv4(start string, inc uint) string {
|
||||
ipv4_new := net.IPv4(v4_o0, v4_o1, v4_o2, v4_o3)
|
||||
return ipv4_new.String()
|
||||
}
|
||||
|
||||
/*
|
||||
Appending the lines to the given file
|
||||
*/
|
||||
func AppendLines(fileName string, lines []string) error {
|
||||
wwlog.Printf(wwlog.VERBOSE, "appending %v lines to %s\n", len(lines), fileName)
|
||||
file, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return errors.Errorf("Can't open file %s: %s", fileName, err)
|
||||
}
|
||||
defer file.Close()
|
||||
for _, line := range lines {
|
||||
wwlog.Printf(wwlog.DEBUG, "Appending '%s' to %s\n", line, fileName)
|
||||
if _, err := file.WriteString(fmt.Sprintf("%s\n", line)); err != nil {
|
||||
return errors.Errorf("Can't write to file %s: %s", fileName, err)
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user