|
|
|
package fs
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"os/user"
|
|
|
|
"path"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"syscall"
|
|
|
|
|
|
|
|
"github.com/op/go-logging"
|
|
|
|
"github.com/subgraph/oz"
|
|
|
|
)
|
|
|
|
|
|
|
|
type directory struct {
|
|
|
|
path string
|
|
|
|
empty bool
|
|
|
|
}
|
|
|
|
|
|
|
|
type Filesystem struct {
|
|
|
|
log *logging.Logger
|
|
|
|
user *user.User
|
|
|
|
name string
|
|
|
|
base string
|
|
|
|
root string
|
|
|
|
xpra string
|
|
|
|
userID string
|
|
|
|
noDefaults bool
|
|
|
|
noSysAndProc bool
|
|
|
|
fullDevices bool
|
|
|
|
whitelist []*mountItem
|
|
|
|
blacklist []*mountItem
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fs *Filesystem) Root() string {
|
|
|
|
return fs.root
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fs *Filesystem) Xpra() string {
|
|
|
|
return fs.xpra
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fs *Filesystem) AddBindWhitelist(path, target string, readonly bool) error {
|
|
|
|
for _, fsitem := range fs.whitelist {
|
|
|
|
if fsitem.path == path {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
item, err := fs.newItem(path, target, readonly)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
fs.whitelist = append(fs.whitelist, item)
|
|
|
|
return item.bindItem()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fs *Filesystem) addWhitelist(path, target string, readonly bool) error {
|
|
|
|
item, err := fs.newItem(path, target, readonly)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
fs.whitelist = append(fs.whitelist, item)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fs *Filesystem) addBlacklist(path string) error {
|
|
|
|
item, err := fs.newItem(path, "", false)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
fs.blacklist = append(fs.blacklist, item)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fs *Filesystem) newItem(path, target string, readonly bool) (*mountItem, error) {
|
|
|
|
p, err := fs.resolveVars(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
t, err := fs.resolveVars(target)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &mountItem{
|
|
|
|
path: p,
|
|
|
|
target: t,
|
|
|
|
//readonly: readonly,
|
|
|
|
fs: fs,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewFromProfile(profile *oz.Profile, user *user.User, basePath string, UseFullDev bool, log *logging.Logger) *Filesystem {
|
|
|
|
fs := NewFilesystem(profile.Name, user, basePath, log)
|
|
|
|
for _, wl := range profile.Whitelist {
|
|
|
|
fs.addWhitelist(wl.Path, wl.Path, wl.ReadOnly)
|
|
|
|
}
|
|
|
|
for _, bl := range profile.Blacklist {
|
|
|
|
fs.addBlacklist(bl.Path)
|
|
|
|
}
|
|
|
|
fs.noDefaults = profile.NoDefaults
|
|
|
|
fs.noSysAndProc = profile.NoSysProc
|
|
|
|
fs.fullDevices = UseFullDev
|
|
|
|
if profile.XServer.Enabled {
|
|
|
|
fs.xpra = path.Join(user.HomeDir, ".Xoz", profile.Name)
|
|
|
|
}
|
|
|
|
return fs
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewFilesystem(name string, user *user.User, basePath string, log *logging.Logger) *Filesystem {
|
|
|
|
fs := new(Filesystem)
|
|
|
|
fs.log = log
|
|
|
|
fs.name = name
|
|
|
|
if log == nil {
|
|
|
|
fs.log = logging.MustGetLogger("oz")
|
|
|
|
}
|
|
|
|
fs.base = path.Join(basePath, name)
|
|
|
|
fs.root = path.Join(fs.base, "rootfs")
|
|
|
|
fs.user = user
|
|
|
|
fs.userID = strconv.Itoa(os.Getuid())
|
|
|
|
|
|
|
|
return fs
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fs *Filesystem) GetHomeDir() (string, error) {
|
|
|
|
if fs.user == nil {
|
|
|
|
return "", fmt.Errorf("Home directory not set")
|
|
|
|
}
|
|
|
|
return fs.user.HomeDir, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
func xcreateEmptyDirectories(base string, paths []string) error {
|
|
|
|
for _, p := range paths {
|
|
|
|
target := path.Join(base, p)
|
|
|
|
if err := createEmptyDir(p, target); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func createEmptyDir(source, target string) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func createSubdirs(base string, subdirs []string) error {
|
|
|
|
for _, sdir := range subdirs {
|
|
|
|
path := path.Join(base, sdir)
|
|
|
|
if err := createDirTree(path); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func createDirTree(path string) error {
|
|
|
|
st, err := os.Stat(path)
|
|
|
|
if err == nil {
|
|
|
|
if !st.IsDir() {
|
|
|
|
return fmt.Errorf("cannot create directory %s because path already exists and is not directory", path)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if !os.IsNotExist(err) {
|
|
|
|
return fmt.Errorf("unexpected error attempting Stat() on path %s: %v", path, err)
|
|
|
|
}
|
|
|
|
if err := os.MkdirAll(path, 0755); err != nil {
|
|
|
|
return fmt.Errorf("error creating directory tree %s: %v", path, err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
// bindMount performs a bind mount of the source path item so that it is visible
|
|
|
|
// at the target path. By default the mount is flagged MS_NOSUID and MS_NODEV
|
|
|
|
// but additional flags can be passed in extraFlags. If the readonly flag is
|
|
|
|
// set the bind mount is remounted as MS_RDONLY.
|
|
|
|
func bindMount(source, target string, readonly bool, extraFlags uintptr) error {
|
|
|
|
flags := syscall.MS_BIND | syscall.MS_NOSUID | syscall.MS_NODEV | extraFlags
|
|
|
|
if err := syscall.Mount(source, target, "", flags, ""); err != nil {
|
|
|
|
return fmt.Errorf("failed to bind mount %s to %s: %v", source, target, err)
|
|
|
|
}
|
|
|
|
if readonly {
|
|
|
|
flags |= syscall.MS_RDONLY | syscall.MS_REMOUNT
|
|
|
|
if err := syscall.Mount("", target, "", flags, ""); err != nil {
|
|
|
|
return fmt.Errorf("failed to remount %s as RDONLY: %v", target, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func createEmptyFile(name string, mode os.FileMode) error {
|
|
|
|
if err := os.MkdirAll(path.Dir(name), 0750); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
fd, err := os.Create(name)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := fd.Close(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := os.Chmod(name, mode); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func copyPathPermissions(root, src string) error {
|
|
|
|
current := "/"
|
|
|
|
for _, part := range strings.Split(src, "/") {
|
|
|
|
if part == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
current = path.Join(current, part)
|
|
|
|
target := path.Join(root, current)
|
|
|
|
if err := copyFilePermissions(current, target); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func copyFilePermissions(src, target string) error {
|
|
|
|
fi, err := os.Stat(src)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return copyFileInfo(fi, target)
|
|
|
|
}
|
|
|
|
|
|
|
|
func copyFileInfo(info os.FileInfo, target string) error {
|
|
|
|
st := info.Sys().(*syscall.Stat_t)
|
|
|
|
os.Chown(target, int(st.Uid), int(st.Gid))
|
|
|
|
os.Chmod(target, info.Mode().Perm())
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func createSubdirs(base string, uid, gid int, mode os.FileMode, subdirs ...string) error {
|
|
|
|
dir := base
|
|
|
|
for _, sd := range subdirs {
|
|
|
|
dir = path.Join(dir, sd)
|
|
|
|
if err := os.Mkdir(dir, mode); err != nil && !os.IsExist(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := os.Chown(dir, uid, gid); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|