mirror of https://github.com/xSmurf/oz.git
commit
6221d0595a
@ -0,0 +1,2 @@
|
||||
oz.iml
|
||||
.idea/
|
@ -0,0 +1,45 @@
|
||||
package oz
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"github.com/op/go-logging"
|
||||
"os"
|
||||
"os/signal"
|
||||
)
|
||||
|
||||
func ReapChildProcs(log *logging.Logger, callback func(int, syscall.WaitStatus)) chan os.Signal {
|
||||
sigs := make(chan os.Signal, 3)
|
||||
signal.Notify(sigs, syscall.SIGCHLD)
|
||||
go func() {
|
||||
for {
|
||||
<-sigs
|
||||
handleSIGCHLD(log, callback)
|
||||
}
|
||||
}()
|
||||
return sigs
|
||||
}
|
||||
|
||||
func handleSIGCHLD(log *logging.Logger, callback func(int, syscall.WaitStatus)) {
|
||||
var wstatus syscall.WaitStatus
|
||||
for {
|
||||
pid,err := syscall.Wait4(-1, &wstatus, syscall.WNOHANG, nil)
|
||||
switch err {
|
||||
case syscall.ECHILD:
|
||||
return
|
||||
|
||||
case syscall.EINTR:
|
||||
|
||||
case nil:
|
||||
if pid == 0 {
|
||||
return
|
||||
}
|
||||
callback(pid, wstatus)
|
||||
|
||||
default:
|
||||
if log != nil {
|
||||
log.Warning("syscall.Wait4() returned error: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package main
|
||||
import (
|
||||
"github.com/subgraph/oz/oz-daemon"
|
||||
)
|
||||
|
||||
func main() {
|
||||
daemon.Main()
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package main
|
||||
import "github.com/subgraph/oz/oz-init"
|
||||
|
||||
func main() {
|
||||
ozinit.Main()
|
||||
}
|
||||
|
@ -0,0 +1,114 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"sort"
|
||||
"strings"
|
||||
"syscall"
|
||||
"errors"
|
||||
"github.com/op/go-logging"
|
||||
)
|
||||
|
||||
func (fs *Filesystem) Cleanup() error {
|
||||
if fs.base == "" {
|
||||
msg := "cannot Cleanup() filesystem, fs.base is empty"
|
||||
fs.log.Warning(msg)
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
for {
|
||||
mnts,err := getMountsBelow(fs.base)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(mnts) == 0 {
|
||||
return nil
|
||||
}
|
||||
atLeastOne, lastErr := mnts.unmountAll(fs.log)
|
||||
if !atLeastOne {
|
||||
return lastErr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (mnts mountEntries) unmountAll(log *logging.Logger) (bool, error) {
|
||||
reterr := error(nil)
|
||||
atLeastOne := false
|
||||
for _,m := range mnts {
|
||||
if err := syscall.Unmount(m.dir, 0); err != nil {
|
||||
log.Warning("Failed to unmount mountpoint %s: %v", m.dir, err)
|
||||
reterr = err
|
||||
} else {
|
||||
atLeastOne = true
|
||||
}
|
||||
}
|
||||
return atLeastOne, reterr
|
||||
}
|
||||
|
||||
type mountEntry struct {
|
||||
src string
|
||||
dir string
|
||||
fs string
|
||||
options string
|
||||
}
|
||||
|
||||
type mountEntries []*mountEntry
|
||||
|
||||
func (m mountEntries) Len() int { return len(m) }
|
||||
func (m mountEntries) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
|
||||
func (m mountEntries) Less(i, j int) bool { return m[i].depth() > m[j].depth() }
|
||||
|
||||
func (me mountEntry) depth() int { return strings.Count(me.dir, "/") }
|
||||
|
||||
func getMountsBelow(base string) (mountEntries, error) {
|
||||
mnts, err := getProcMounts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sort.Sort(mnts)
|
||||
var filtered mountEntries
|
||||
for _, m := range mnts {
|
||||
if strings.HasPrefix(m.dir, base) {
|
||||
filtered = append(filtered, m)
|
||||
}
|
||||
}
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
func (m mountEntries) contains(dir string) bool {
|
||||
for _, mnt := range m {
|
||||
if dir == mnt.dir {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getProcMounts() (mountEntries, error) {
|
||||
lines, err := readProcMounts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var entries mountEntries
|
||||
for _, line := range lines {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) >= 4 {
|
||||
entries = append(entries, &mountEntry{
|
||||
src: parts[0],
|
||||
dir: parts[1],
|
||||
fs: parts[2],
|
||||
options: parts[3],
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func readProcMounts() ([]string, error) {
|
||||
content, err := ioutil.ReadFile("/proc/mounts")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return strings.Split(string(content), "\n"), nil
|
||||
}
|
@ -0,0 +1,207 @@
|
||||
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
|
||||
home string
|
||||
base string
|
||||
root string
|
||||
userID string
|
||||
noDefaults bool
|
||||
noSysAndProc bool
|
||||
whitelist []*mountItem
|
||||
blacklist []*mountItem
|
||||
}
|
||||
|
||||
func (fs *Filesystem) Root() string {
|
||||
return fs.root
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
return &mountItem{
|
||||
path: p,
|
||||
target: target,
|
||||
//readonly: readonly,
|
||||
fs: fs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewFromProfile(profile *oz.Profile, log *logging.Logger) *Filesystem {
|
||||
fs := NewFilesystem(profile.Name, 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
|
||||
return fs
|
||||
}
|
||||
|
||||
func NewFilesystem(name string, log *logging.Logger) *Filesystem {
|
||||
|
||||
fs := new(Filesystem)
|
||||
fs.log = log
|
||||
if log == nil {
|
||||
fs.log = logging.MustGetLogger("oz")
|
||||
}
|
||||
fs.base = path.Join("/srv/oz", name)
|
||||
fs.root = path.Join(fs.base, "rootfs")
|
||||
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
panic("Failed to look up current user: " + err.Error())
|
||||
}
|
||||
fs.home = u.HomeDir
|
||||
fs.userID = strconv.Itoa(os.Getuid())
|
||||
|
||||
return fs
|
||||
}
|
||||
|
||||
/*
|
||||
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
|
||||
}
|
@ -0,0 +1,183 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"github.com/op/go-logging"
|
||||
)
|
||||
|
||||
type MountFlag int
|
||||
|
||||
func (mf MountFlag) isSet(f MountFlag) bool {
|
||||
return mf&f == f
|
||||
}
|
||||
|
||||
const (
|
||||
MountReadOnly MountFlag = 1 << iota
|
||||
MountCreateIfAbsent
|
||||
)
|
||||
|
||||
type mountItem struct {
|
||||
path string
|
||||
target string
|
||||
flags MountFlag
|
||||
fs *Filesystem
|
||||
}
|
||||
|
||||
func (mi *mountItem) targetPath() string {
|
||||
root := mi.fs.root
|
||||
if mi.target != "" {
|
||||
return path.Join(root, mi.target)
|
||||
}
|
||||
return path.Join(root, mi.path)
|
||||
}
|
||||
|
||||
func (mi *mountItem) bind() error {
|
||||
if strings.Contains(mi.path, "*") {
|
||||
return mi.bindGlobbed()
|
||||
}
|
||||
return mi.bindItem()
|
||||
}
|
||||
|
||||
func (mi *mountItem) bindGlobbed() error {
|
||||
if mi.target != "" {
|
||||
mi.fs.log.Warning("Ignoring target directory (%s) for mount item containing glob character: (%s)", mi.target, mi.path)
|
||||
mi.target = ""
|
||||
}
|
||||
globbed, err := filepath.Glob(mi.path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
savedPath := mi.path
|
||||
for _, p := range globbed {
|
||||
if strings.Contains(p, "*") {
|
||||
// XXX
|
||||
continue
|
||||
}
|
||||
mi.path = p
|
||||
if err := mi.bind(); err != nil {
|
||||
// XXX
|
||||
mi.path = savedPath
|
||||
return err
|
||||
}
|
||||
}
|
||||
mi.path = savedPath
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mi *mountItem) readSourceInfo(src string) (os.FileInfo, error) {
|
||||
if fi, err := os.Stat(src); err == nil {
|
||||
return fi, nil
|
||||
} else if !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !mi.flags.isSet(MountCreateIfAbsent) {
|
||||
return nil, fmt.Errorf("source path (%s) does not exist", src)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(src, mi.fs.home) {
|
||||
return nil, fmt.Errorf("mount item (%s) has flag MountCreateIfAbsent, but is not child of home directory (%s)", src, mi.fs.home)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(src, 0750); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pinfo, err := os.Stat(path.Dir(src))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := copyFileInfo(pinfo, src); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return os.Stat(src)
|
||||
}
|
||||
|
||||
func (mi *mountItem) bindItem() error {
|
||||
src, err := filepath.EvalSymlinks(mi.path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error resolving symlinks for path (%s): %v", mi.path, err)
|
||||
}
|
||||
|
||||
sinfo, err := mi.readSourceInfo(src)
|
||||
if err != nil {
|
||||
// XXX
|
||||
return err
|
||||
}
|
||||
|
||||
target := mi.targetPath()
|
||||
_, err = os.Stat(target)
|
||||
if err == nil || !os.IsNotExist(err) {
|
||||
mi.fs.log.Warning("Target (%s) already exists, ignoring", target)
|
||||
return nil
|
||||
}
|
||||
if sinfo.IsDir() {
|
||||
if err := os.MkdirAll(target, sinfo.Mode().Perm()); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := createEmptyFile(target, 0750); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := copyPathPermissions(mi.fs.root, src); err != nil {
|
||||
return fmt.Errorf("failed to copy path permissions for (%s): %v", src, err)
|
||||
}
|
||||
return bindMount(src, target, mi.flags.isSet(MountReadOnly), 0)
|
||||
}
|
||||
|
||||
func (mi *mountItem) blacklist() error {
|
||||
if strings.Contains(mi.path, "*") {
|
||||
return mi.blacklistGlobbed()
|
||||
}
|
||||
return blacklistItem(mi.path, mi.fs.log)
|
||||
}
|
||||
|
||||
func (mi *mountItem) blacklistGlobbed() error {
|
||||
globbed, err := filepath.Glob(mi.path)
|
||||
if err != nil {
|
||||
// XXX
|
||||
}
|
||||
for _, p := range globbed {
|
||||
if err := blacklistItem(p, mi.fs.log); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func blacklistItem(path string, log *logging.Logger) error {
|
||||
p, err := filepath.EvalSymlinks(path)
|
||||
if err != nil {
|
||||
log.Warning("Symlink evaluation failed for path: %s", path)
|
||||
return err
|
||||
}
|
||||
fi, err := os.Stat(p)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
log.Info("Blacklist item (%s) does not exist", p)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
src := emptyFilePath
|
||||
if fi.IsDir() {
|
||||
src = emptyDirPath
|
||||
}
|
||||
if err := syscall.Mount(src, p, "none", syscall.MS_BIND, "mode=400,gid=0"); err != nil {
|
||||
// XXX warning
|
||||
return err
|
||||
}
|
||||
// XXX log success
|
||||
|
||||
return nil
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// OzInit is run from the oz-init process and performs post chroot filesystem initialization
|
||||
func (fs *Filesystem) OzInit() error {
|
||||
if err := fs.ozinitMountDev(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fs.ozinitMountSysProc(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fs.ozinitCreateSymlinks(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fs.ozinitBlacklistItems(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *Filesystem) ozinitMountDev() error {
|
||||
flags := uintptr(syscall.MS_NOSUID | syscall.MS_REC | syscall.MS_NOEXEC)
|
||||
if err := syscall.Mount("none", "/dev", "devtmpfs", flags, ""); err != nil {
|
||||
fs.log.Warning("Failed to mount devtmpfs: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := mountSpecial("/dev/shm", "tmpfs"); err != nil {
|
||||
fs.log.Warning("Failed to mount shm directory: %v", err)
|
||||
return err
|
||||
}
|
||||
if err := mountSpecial("/dev/pts", "devpts"); err != nil {
|
||||
fs.log.Warning("Failed to mount pts directory: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mountSpecial(path, mtype string) error {
|
||||
flags := uintptr(syscall.MS_NOSUID | syscall.MS_REC | syscall.MS_NOEXEC)
|
||||
if err := os.MkdirAll(path, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return syscall.Mount(path, path, mtype, flags, "")
|
||||
}
|
||||
|
||||
func (fs *Filesystem) ozinitMountSysProc() error {
|
||||
if fs.noSysAndProc {
|
||||
return nil
|
||||
}
|
||||
flags := uintptr(syscall.MS_NOSUID | syscall.MS_REC | syscall.MS_NOEXEC | syscall.MS_NODEV)
|
||||
proc := "/proc"
|
||||
if err := syscall.Mount("proc", proc, "proc", flags, ""); err != nil {
|
||||
fs.log.Warning("Failed to mount /proc: %v", err)
|
||||
return err
|
||||
}
|
||||
roMounts := []string{
|
||||
"sysrq-trigger",
|
||||
"bus",
|
||||
"irq",
|
||||
"sys/kernel/hotplug",
|
||||
}
|
||||
for _, rom := range roMounts {
|
||||
p := path.Join(proc, rom)
|
||||
if err := bindMount(p, p, true, 0); err != nil {
|
||||
fs.log.Warning("Failed to RO mount %s: %v", p, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := syscall.Mount("sysfs", "/sys", "sysfs", syscall.MS_RDONLY|flags, ""); err != nil {
|
||||
fs.log.Warning("Failed to mount /sys: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *Filesystem) ozinitCreateSymlinks() error {
|
||||
for _, sl := range basicSymlinks {
|
||||
if err := syscall.Symlink(sl[0], sl[1]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *Filesystem) ozinitBlacklistItems() error {
|
||||
if err := createBlacklistDir(emptyDirPath); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := createBlacklistFile(emptyFilePath); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, item := range fs.blacklist {
|
||||
if err := item.blacklist(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createBlacklistDir(path string) error {
|
||||
if err := os.MkdirAll(path, 0000); err != nil {
|
||||
return err
|
||||
}
|
||||
return setBlacklistPerms(path, 0500)
|
||||
}
|
||||
|
||||
func createBlacklistFile(path string) error {
|
||||
fd, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fd.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return setBlacklistPerms(path, 0400)
|
||||
}
|
||||
|
||||
func setBlacklistPerms(path string, mode os.FileMode) error {
|
||||
if err := os.Chown(path, 0, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Chmod(path, mode); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (fs *Filesystem) resolvePath(p string) ([]string, error) {
|
||||
p, err := fs.resolveVars(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fs.resolveGlob(p)
|
||||
}
|
||||
|
||||
func (fs *Filesystem) resolveVars(p string) (string, error) {
|
||||
const pathVar = "${PATH}/"
|
||||
const homeVar = "${HOME}"
|
||||
const uidVar = "${UID}"
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(p, pathVar):
|
||||
resolved, err := exec.LookPath(p[len(pathVar):])
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to resolve %s", p)
|
||||
}
|
||||
return resolved, nil
|
||||
|
||||
case strings.HasPrefix(p, homeVar):
|
||||
return path.Join(fs.home, p[len(homeVar):]), nil
|
||||
|
||||
case strings.HasPrefix(p, uidVar):
|
||||
return strings.Replace(p, uidVar, fs.userID, -1), nil
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (fs *Filesystem) resolveGlob(p string) ([]string, error) {
|
||||
if !strings.Contains(p, "*") {
|
||||
return []string{p}, nil
|
||||
}
|
||||
list, err := filepath.Glob(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to glob resolve %s: %v", p, err)
|
||||
}
|
||||
return list, nil
|
||||
}
|
@ -0,0 +1,144 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var basicBindDirs = []string{
|
||||
"/bin", "/lib", "/lib64", "/usr", "/etc", "/var/lib/oz",
|
||||
}
|
||||
|
||||
var basicEmptyDirs = []string{
|
||||
"/sbin", "/var", "/var/lib",
|
||||
"/var/cache", "/home", "/boot",
|
||||
"/tmp", "/run", "/run/user",
|
||||
"/run/lock", "/root", "/opt",
|
||||
"/srv", "/dev", "/proc", "/sys",
|
||||
"/mnt", "/media",
|
||||
}
|
||||
|
||||
var basicBlacklist = []string{
|
||||
"/usr/sbin", "/sbin", "${PATH}/su",
|
||||
"${PATH}/sudo", "${PATH}/fusermount",
|
||||
"${PATH}/xinput", "${PATH}/strace",
|
||||
"${PATH}/mount", "${PATH}/umount",
|
||||
}
|
||||
|
||||
const emptyFilePath = "/tmp/oz.ro.file"
|
||||
const emptyDirPath = "/tmp/oz.ro.dir"
|
||||
|
||||
var basicSymlinks = [][2]string{
|
||||
{"/run", "/var/run"},
|
||||
{"/tmp", "/var/tmp"},
|
||||
{"/run/lock", "/var/lock"},
|
||||
}
|
||||
|
||||
func (fs *Filesystem) Setup() error {
|
||||
if err := fs.setupRootfs(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fs.setupChroot(); err != nil {
|
||||
return err
|
||||
}
|
||||
return fs.setupMountItems()
|
||||
}
|
||||
|
||||
func (fs *Filesystem) setupRootfs() error {
|
||||
if err := os.MkdirAll(fs.base, 0755); err != nil {
|
||||
return fmt.Errorf("unable to create directory (%s): %v", fs.base, err)
|
||||
}
|
||||
flags := uintptr(syscall.MS_NOSUID | syscall.MS_NOEXEC | syscall.MS_NODEV)
|
||||
data := "mode=755,gid=0"
|
||||
if err := syscall.Mount(fs.base, fs.base, "tmpfs", flags, data); err != nil {
|
||||
return fmt.Errorf("failed to create base tmpfs at %s: %v", fs.base, err)
|
||||
}
|
||||
// create extra directories
|
||||
extra := []string{"sockets"}
|
||||
for _, sub := range extra {
|
||||
d := path.Join(fs.base, sub)
|
||||
if err := os.Mkdir(d, 0755); err != nil {
|
||||
return fmt.Errorf("unable to create directory (%s): %v", d, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *Filesystem) setupChroot() error {
|
||||
var err error
|
||||
if fs.noDefaults {
|
||||
err = createEmptyDirectories(fs.root, basicBindDirs)
|
||||
} else {
|
||||
err = bindBasicDirectories(fs.root, basicBindDirs)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = createEmptyDirectories(fs.root, basicEmptyDirs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return setupTmp(fs.root)
|
||||
}
|
||||
|
||||
func bindBasicDirectories(root string, dirs []string) error {
|
||||
for _, src := range dirs {
|
||||
st, err := os.Lstat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mode := st.Mode()
|
||||
target := path.Join(root, src)
|
||||
if err := os.MkdirAll(target, mode.Perm()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := bindMount(src, target, true, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createEmptyDirectories(root string, dirs []string) error {
|
||||
for _, p := range dirs {
|
||||
target := path.Join(root, p)
|
||||
if err := createEmptyDirectory(p, target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createEmptyDirectory(source, target string) error {
|
||||
fi, err := os.Stat(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mode := fi.Mode()
|
||||
if err := os.MkdirAll(target, mode.Perm()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := copyFileInfo(fi, target); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setupTmp(root string) error {
|
||||
target := path.Join(root, "tmp")
|
||||
if err := os.Chmod(target, 0777); err != nil {
|
||||
return err
|
||||
}
|
||||
return bindMount(target, target, false, syscall.MS_NOEXEC)
|
||||
}
|
||||
|
||||
func (fs *Filesystem) setupMountItems() error {
|
||||
for _, item := range fs.whitelist {
|
||||
if err := item.bind(); err != nil {
|
||||
// XXX
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
package ipc
|
||||
import (
|
||||
"reflect"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type handlerMap map[string]reflect.Value
|
||||
|
||||
func (handlers handlerMap) dispatch(m *Message) error {
|
||||
h,ok := handlers[m.Type]
|
||||
if !ok {
|
||||
return errors.New("no handler found for message type:"+ m.Type)
|
||||
}
|
||||
return executeHandler(h, m)
|
||||
}
|
||||
|
||||
func executeHandler(h reflect.Value, m *Message) error {
|
||||
var args [2]reflect.Value
|
||||
args[0] = reflect.ValueOf(m.Body)
|
||||
args[1]= reflect.ValueOf(m)
|
||||
|
||||
rs := h.Call(args[:])
|
||||
if len(rs) != 1 {
|
||||
return errors.New("handler function did not return expected single result value")
|
||||
}
|
||||
if rs[0].IsNil() {
|
||||
return nil
|
||||
}
|
||||
return rs[0].Interface().(error)
|
||||
}
|
||||
|
||||
func (handlers handlerMap) addHandler(h interface{}) error {
|
||||
msgType, err := typeCheckHandler(h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _,ok := handlers[msgType]; ok{
|
||||
return fmt.Errorf("duplicate handler registered for message type '%s'", msgType)
|
||||
}
|
||||
handlers[msgType] = reflect.ValueOf(h)
|
||||
return nil
|
||||
}
|
||||
|
||||
var errType = reflect.TypeOf((*error)(nil)).Elem()
|
||||
var messageType = reflect.TypeOf((*Message)(nil))
|
||||
|
||||
func typeCheckHandler(h interface{}) (string, error) {
|
||||
t := reflect.TypeOf(h)
|
||||
if t.Kind() != reflect.Func {
|
||||
return "", fmt.Errorf("handler %v is not a function", t)
|
||||
}
|
||||
if t.NumIn() != 2 {
|
||||
return "", fmt.Errorf("handler %v has incorrect number of input arguments, got %d", t, t.NumIn())
|
||||
}
|
||||
if t.NumOut() != 1 {
|
||||
return "", fmt.Errorf("handler %v has incorrect number of return values %d", t, t.NumOut())
|
||||
}
|
||||
if t.In(0).Kind() != reflect.Ptr {
|
||||
return "", errors.New("first argument of handler is not a pointer")
|
||||
}
|
||||
in0 := t.In(0).Elem()
|
||||
if in0.Kind() != reflect.Struct {
|
||||
return "", fmt.Errorf("first argument of handler is not a pointer to struct")
|
||||
}
|
||||
if in1 := t.In(1); !in1.AssignableTo(messageType) {
|
||||
return "", fmt.Errorf("second argument of handler must have type *Message")
|
||||
}
|
||||
if out := t.Out(0); !out.AssignableTo(errType) {
|
||||
return "", fmt.Errorf("return type of handler must be error")
|
||||
}
|
||||
|
||||
if in0.NumField() == 0 {
|
||||
return "", fmt.Errorf("first argument structure has no fields")
|
||||
}
|
||||
if len(in0.Field(0).Tag) == 0 {
|
||||
return "", fmt.Errorf("first argument structure, first field has no tag")
|
||||
}
|
||||
return string(in0.Field(0).Tag), nil
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,86 @@
|
||||
package ipc
|
||||
import (
|
||||
"testing"
|
||||
"reflect"
|
||||
"errors"
|
||||
)
|
||||
|
||||
func TestTypeCheckHandler(t *testing.T) {
|
||||
type testStruct struct {}
|
||||
|
||||
cases := []interface{} {
|
||||
"foo",
|
||||
func(){},
|
||||
func(a,b int){},
|
||||
func(a *testStruct, b *Message) {},
|
||||
func(a,b *int) error{return nil},
|
||||
func(a *testStruct, b int) error {return nil},
|
||||
func(a *testStruct, b Message) error {return nil},
|
||||
func(a *testStruct, b *Message) int{return 0},
|
||||
func(a *testStruct, b *Message, c int) error {return nil},
|
||||
}
|
||||
|
||||
for i,h := range cases {
|
||||
if _,err := typeCheckHandler(h); err == nil {
|
||||
t.Errorf("typeCheckHandler should return an error for case %d", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddHandler(t *testing.T) {
|
||||
hmap := handlerMap(make(map[string]reflect.Value))
|
||||
type testStruct struct{
|
||||
t int "tst"
|
||||
}
|
||||
legit := func(ts *testStruct, m *Message) error { return nil }
|
||||
|
||||
if err := hmap.addHandler("bar"); err == nil {
|
||||
t.Error("attempt to register string as handler function did not fail as expected")
|
||||
}
|
||||
|
||||
if err := hmap.addHandler(legit); err != nil {
|
||||
t.Error("registration of good handler function failed:", err)
|
||||
}
|
||||
|
||||
if err := hmap.addHandler(legit); err == nil {
|
||||
t.Error("registration of duplicate handler function did not fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDispatch(t *testing.T) {
|
||||
type testStruct struct{ t int "tester"}
|
||||
type testStruct2 struct{ t int "tester2"}
|
||||
count := 0
|
||||
h1 := func(ts *testStruct, m *Message) error {
|
||||
count += 1
|
||||
return nil
|
||||
}
|
||||
|
||||
h2 := func(ts *testStruct2, m *Message) error {
|
||||
count += 1
|
||||
return errors.New("...")
|
||||
}
|
||||
|
||||
hmap := handlerMap(make(map[string]reflect.Value))
|
||||
if err := hmap.addHandler(h1); err != nil {
|
||||
t.Errorf("unexpected failure to register handler: %v", err)
|
||||
}
|
||||
if err := hmap.addHandler(h2); err != nil {
|
||||
t.Errorf("unexpected failure to register handler: %v", err)
|
||||
}
|
||||
m := new(Message)
|
||||
m.Type = "tester"
|
||||
m.Body = new(testStruct)
|
||||
if err := hmap.dispatch(m); err != nil {
|
||||
t.Error("unexpected error calling dispatch():", err)
|
||||
}
|
||||
m.Type = "tester2"
|
||||
m.Body = new(testStruct2)
|
||||
if err := hmap.dispatch(m); err == nil {
|
||||
t.Errorf("dispatch() did not return error as expected")
|
||||
|
||||
}
|
||||
if count != 2 {
|
||||
t.Errorf("count was not incremented to 2 as expected. count = %d", count)
|
||||
}
|
||||
}
|
@ -0,0 +1,288 @@
|
||||
package ipc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"github.com/op/go-logging"
|
||||
"reflect"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const maxFdCount = 3
|
||||
|
||||
var log = logging.MustGetLogger("oz")
|
||||
|
||||
type MsgConn struct {
|
||||
msgs chan *Message
|
||||
addr *net.UnixAddr
|
||||
conn *net.UnixConn
|
||||
buf [1024]byte
|
||||
oob []byte
|
||||
handlers handlerMap
|
||||
factory MsgFactory
|
||||
isClosed bool
|
||||
done chan bool
|
||||
idGen <-chan int
|
||||
respMan *responseManager
|
||||
}
|
||||
|
||||
func NewMsgConn(factory MsgFactory, address string) *MsgConn {
|
||||
mc := new(MsgConn)
|
||||
mc.addr = &net.UnixAddr{address, "unixgram"}
|
||||
mc.oob = createOobBuffer()
|
||||
mc.msgs = make(chan *Message)
|
||||
mc.handlers = make(map[string]reflect.Value)
|
||||
mc.factory = factory
|
||||
mc.done = make(chan bool)
|
||||
mc.idGen = newIdGen(mc.done)
|
||||
mc.respMan = newResponseManager()
|
||||
return mc
|
||||
}
|
||||
|
||||
func newIdGen(done <-chan bool) <-chan int {
|
||||
ch := make(chan int)
|
||||
go idGenLoop(done, ch)
|
||||
return ch
|
||||
}
|
||||
|
||||
func idGenLoop(done <-chan bool, out chan <- int) {
|
||||
current := int(1)
|
||||
for {
|
||||
select {
|
||||
case out <- current:
|
||||
current += 1
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (mc *MsgConn) Listen() error {
|
||||
if mc.conn != nil {
|
||||
return errors.New("cannot Listen(), already connected")
|
||||
}
|
||||
conn, err := net.ListenUnixgram("unixgram", mc.addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := setPassCred(conn); err != nil {
|
||||
return err
|
||||
}
|
||||
mc.conn = conn
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mc *MsgConn) Connect() error {
|
||||
if mc.conn != nil {
|
||||
return errors.New("cannot Connect(), already connected")
|
||||
}
|
||||
clientAddr,err := CreateRandomAddress("@oz-")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn, err := net.DialUnix("unixgram", &net.UnixAddr{clientAddr, "unixgram"}, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mc.conn = conn
|
||||
go mc.readLoop()
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateRandomAddress(prefix string) (string,error) {
|
||||
var bs [16]byte
|
||||
n,err := rand.Read(bs[:])
|
||||
if n != len(bs) {
|
||||
return "", errors.New("incomplete read of random bytes for client name")
|
||||
}
|
||||
if err != nil {
|
||||
return "", errors.New("error reading random bytes for client name: "+ err.Error())
|
||||
}
|
||||
return prefix+ hex.EncodeToString(bs[:]),nil
|
||||
}
|
||||
|
||||
func (mc *MsgConn) Run() error {
|
||||
go mc.readLoop()
|
||||
for m := range mc.msgs {
|
||||
if err := mc.handlers.dispatch(m); err != nil {
|
||||
return fmt.Errorf("error dispatching message: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mc *MsgConn) readLoop() {
|
||||
for {
|
||||
if mc.processOneMessage() {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (mc *MsgConn) processOneMessage() bool {
|
||||
m,err := mc.readMessage()
|
||||
if err != nil {
|
||||
close(mc.msgs)
|
||||
if !mc.isClosed {
|
||||
log.Warning("error on MsgConn.readMessage(): %v", err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
if !mc.respMan.handle(m) {
|
||||
mc.msgs <- m
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (mc *MsgConn) Close() error {
|
||||
mc.isClosed = true
|
||||
close(mc.done)
|
||||
return mc.conn.Close()
|
||||
}
|
||||
|
||||
func createOobBuffer() []byte {
|
||||
oobSize := syscall.CmsgSpace(syscall.SizeofUcred) + syscall.CmsgSpace(4*maxFdCount)
|
||||
return make([]byte, oobSize)
|
||||
}
|
||||
|
||||
func (mc *MsgConn) readMessage() (*Message, error) {
|
||||
n, oobn, _, a, err := mc.conn.ReadMsgUnix(mc.buf[:], mc.oob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m, err := mc.parseMessage(mc.buf[:n])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.mconn = mc
|
||||
m.Peer = a
|
||||
|
||||
if oobn > 0 {
|
||||
err := m.parseControlData(mc.oob[:oobn])
|
||||
if err != nil {
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// AddHandlers registers a list of message handling functions with a MsgConn instance.
|
||||
// Each handler function must have two arguments and return a single error value. The
|
||||
// first argument must be pointer to a message structure type. A message structure type
|
||||
// is a structure that must have a struct tag on the first field:
|
||||
//
|
||||
// type FooMsg struct {
|
||||
// Stuff string "Foo" // <------ struct tag
|
||||
// // etc...
|
||||
// }
|
||||
//
|
||||
// type SimpleMsg struct {
|
||||
// dummy int "Simple" // struct has no fields, so add an unexported dummy field just for the tag
|
||||
// }
|
||||
//
|
||||
// The second argument to a handler function must have type *ipc.Message. After a handler function
|
||||
// has been registered, received messages matching the first argument will be dispatched to the corresponding
|
||||
// handler function.
|
||||
//
|
||||
// func fooHandler(foo *FooMsg, msg *ipc.Message) error { /* ... */ }
|
||||
// func simpleHandler(simple *SimpleMsg, msg *ipc.Message) error { /* ... */ }
|
||||
//
|
||||
// /* register fooHandler() to handle incoming FooMsg and SimpleHandler to handle SimpleMsg */
|
||||
// conn.AddHandlers(fooHandler, simpleHandler)
|
||||
//
|
||||
|
||||
|
||||
func (mc *MsgConn) AddHandlers(args ...interface{}) error {
|
||||
for len(args) > 0 {
|
||||
if err := mc.handlers.addHandler(args[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
args = args[1:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mc *MsgConn) SendMsg(msg interface{}, fds... int) error {
|
||||
return mc.sendMessage(msg, <-mc.idGen, mc.addr, fds...)
|
||||
}
|
||||
|
||||
func (mc *MsgConn) ExchangeMsg(msg interface{}, fds... int) (ResponseReader, error) {
|
||||
id := <-mc.idGen
|
||||
rr := mc.respMan.register(id)
|
||||
|
||||
if err := mc.sendMessage(msg, id, mc.addr, fds...); err != nil {
|
||||
rr.Done()
|
||||
return nil, err
|
||||
}
|
||||
return rr,nil
|
||||
}
|
||||
|
||||
func (mc *MsgConn) sendMessage(msg interface{}, msgID int, dst *net.UnixAddr, fds... int) error {
|
||||
msgType, err := getMessageType(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
base, err := mc.newBaseMessage(msgType, msgID, msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
raw, err := json.Marshal(base)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return mc.sendRaw(raw, dst, fds...)
|
||||
}
|
||||
|
||||
func getMessageType(msg interface{}) (string, error) {
|
||||
t := reflect.TypeOf(msg)
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
if t.Kind() != reflect.Struct {
|
||||
return "", fmt.Errorf("sendMessage() msg (%T) is not a struct", msg)
|
||||
}
|
||||
if t.NumField() == 0 || len(t.Field(0).Tag) == 0 {
|
||||
return "", fmt.Errorf("sendMessage() msg struct (%T) does not have tag on first field")
|
||||
}
|
||||
return string(t.Field(0).Tag), nil
|
||||
}
|
||||
|
||||
|
||||
func (mc *MsgConn) newBaseMessage(msgType string, msgID int, body interface{}) (*BaseMsg, error) {
|
||||
bodyBytes,err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
base := new(BaseMsg)
|
||||
base.Type = msgType
|
||||
base.MsgID = msgID
|
||||
base.Body = bodyBytes
|
||||
return base, nil
|
||||
}
|
||||
|
||||
func (mc *MsgConn) sendRaw(data []byte, dst *net.UnixAddr, fds ...int) error {
|
||||
if len(fds) > 0 {
|
||||
return mc.sendWithFds(data, dst, fds)
|
||||
}
|
||||
return mc.write(data, dst)
|
||||
}
|
||||
|
||||
func (mc *MsgConn) write(data []byte, dst *net.UnixAddr) error {
|
||||
if dst != nil {
|
||||
_,err := mc.conn.WriteToUnix(data, dst)
|
||||
return err
|
||||
}
|
||||
_,err := mc.conn.Write(data)
|
||||
return err
|
||||
}
|
||||
|
||||
func (mc *MsgConn) sendWithFds(data []byte, dst *net.UnixAddr, fds []int) error {
|
||||
oob := syscall.UnixRights(fds...)
|
||||
_,_,err := mc.conn.WriteMsgUnix(data, oob, dst)
|
||||
return err
|
||||
}
|
||||
|
@ -0,0 +1,100 @@
|
||||
package ipc
|
||||
import (
|
||||
"testing"
|
||||
"sync"
|
||||
"os"
|
||||
)
|
||||
|
||||
type TestMsg struct {
|
||||
t int "Test"
|
||||
}
|
||||
|
||||
type testConnection struct {
|
||||
server *MsgConn
|
||||
client *MsgConn
|
||||
wg sync.WaitGroup
|
||||
called bool
|
||||
}
|
||||
|
||||
type testServer struct {
|
||||
conn *MsgConn
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
const testSocket = "@test"
|
||||
var testFactory = NewMsgFactory(new(TestMsg))
|
||||
|
||||
func testConnect(handler func(*TestMsg, *Message) error) (*testConnection, error) {
|
||||
s := NewMsgConn(testFactory, testSocket)
|
||||
c := NewMsgConn(testFactory, testSocket)
|
||||
tc := &testConnection{
|
||||
server: s,
|
||||
client: c,
|
||||
}
|
||||
wrapper := func(tm *TestMsg, msg *Message) error {
|
||||
err := handler(tm, msg)
|
||||
tc.called = true
|
||||
tc.wg.Done()
|
||||
return err
|
||||
}
|
||||
if err := s.AddHandlers(wrapper); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.Listen(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.Connect(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tc.wg.Add(1)
|
||||
go tc.server.Run()
|
||||
return tc, nil
|
||||
}
|
||||
|
||||
func runTest(t *testing.T, handler func(*TestMsg, *Message) error, tester func(*testConnection)) {
|
||||
tc,err := testConnect(handler)
|
||||
if err != nil {
|
||||
t.Error("error setting up test connection:", err)
|
||||
}
|
||||
tester(tc)
|
||||
tc.wait()
|
||||
if !tc.called {
|
||||
t.Error("handler function not called")
|
||||
}
|
||||
}
|
||||
|
||||
func (tc *testConnection) wait() {
|
||||
tc.wg.Wait()
|
||||
tc.client.Close()
|
||||
tc.server.Close()
|
||||
}
|
||||
|
||||
func TestUcred(t *testing.T) {
|
||||
handler := func(tm *TestMsg, msg *Message) error {
|
||||
uid := uint32(os.Getuid())
|
||||
gid := uint32(os.Getgid())
|
||||
pid := int32(os.Getpid())
|
||||
u := msg.Ucred
|
||||
if u.Uid != uid || u.Gid != gid || u.Pid != pid {
|
||||
t.Errorf("ucred (%d/%d/%d) does not match process (%d/%d/%d)", u.Uid, u.Gid, u.Pid, uid, gid, pid)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
runTest(t, handler, func(tc *testConnection) {
|
||||
tc.client.SendMsg(&TestMsg{})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestPassFDs(t *testing.T) {
|
||||
fds := []int{1,2}
|
||||
handler := func(tm *TestMsg, msg *Message) error {
|
||||
if len(msg.Fds) != len(fds) {
|
||||
t.Errorf("Expecting %d descriptors, got %d", len(fds), len(msg.Fds))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
runTest(t, handler, func(tc *testConnection) {
|
||||
tc.client.SendMsg(&TestMsg{}, fds...)
|
||||
|
||||
})
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
package ipc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"syscall"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"errors"
|
||||
)
|
||||
|
||||
|
||||
|
||||
func NewMsgFactory(msgTypes ...interface{}) MsgFactory {
|
||||
mf := (MsgFactory)(make(map[string]func() interface{}))
|
||||
for _, mt := range msgTypes {
|
||||
if err := mf.register(mt); err != nil {
|
||||
log.Fatalf("failed adding (%T) in NewMsgFactory: %v", mt, err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return mf
|
||||
}
|
||||
|
||||
type MsgFactory map[string](func() interface{})
|
||||
|
||||
func (mf MsgFactory) create(msgType string) (interface{}, error) {
|
||||
f,ok := mf[msgType]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot create msg type: %s", msgType)
|
||||
}
|
||||
return f(), nil
|
||||
}
|
||||
|
||||
func (mf MsgFactory) register(mt interface{}) error {
|
||||
t := reflect.TypeOf(mt)
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
if t.Kind() != reflect.Struct {
|
||||
return errors.New("not a structure")
|
||||
}
|
||||
if t.NumField() == 0 || len(t.Field(0).Tag) == 0 {
|
||||
return errors.New("no tag on first field of structure")
|
||||
}
|
||||
tag := string(t.Field(0).Tag)
|
||||
|
||||
mf[tag] = func() interface{} {
|
||||
v := reflect.New(t)
|
||||
return v.Interface()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Type string
|
||||
MsgID int
|
||||
Body interface{}
|
||||
Peer *net.UnixAddr
|
||||
Ucred *syscall.Ucred
|
||||
Fds []int
|
||||
mconn *MsgConn
|
||||
}
|
||||
|
||||
type BaseMsg struct {
|
||||
Type string
|
||||
MsgID int
|
||||
IsResponse bool
|
||||
Body json.RawMessage
|
||||
}
|
||||
|
||||
func (mc *MsgConn) parseMessage(data []byte) (*Message, error) {
|
||||
var base BaseMsg
|
||||
if err := json.Unmarshal(data, &base); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body,err := mc.factory.create(base.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(base.Body, body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := new(Message)
|
||||
m.Type = base.Type
|
||||
m.MsgID = base.MsgID
|
||||
m.Body = body
|
||||
return m,nil
|
||||
}
|
||||
|
||||
func (m *Message) Free() {
|
||||
for _,fd := range m.Fds {
|
||||
syscall.Close(fd)
|
||||
}
|
||||
m.Fds = nil
|
||||
}
|
||||
|
||||
func (m *Message) parseControlData(data []byte) error {
|
||||
cmsgs, err := syscall.ParseSocketControlMessage(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, cmsg := range cmsgs {
|
||||
switch cmsg.Header.Type {
|
||||
case syscall.SCM_CREDENTIALS:
|
||||
cred, err := syscall.ParseUnixCredentials(&cmsg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.Ucred = cred
|
||||
case syscall.SCM_RIGHTS:
|
||||
fds, err := syscall.ParseUnixRights(&cmsg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.Fds = fds
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Message) Respond(msg interface{}, fds... int) error {
|
||||
return m.mconn.sendMessage(msg, m.MsgID, m.Peer, fds...)
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
package ipc
|
||||
import (
|
||||
"time"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type ResponseReader interface {
|
||||
Chan() <-chan *Message
|
||||
Done()
|
||||
}
|
||||
|
||||
type responseWaiter struct {
|
||||
rm *responseManager
|
||||
id int
|
||||
timeout time.Time
|
||||
ch chan *Message
|
||||
}
|
||||
|
||||
func (rw *responseWaiter) Chan() <-chan *Message {
|
||||
return rw.ch
|
||||
}
|
||||
|
||||
func (rw *responseWaiter) Done() {
|
||||
rw.rm.lock.Lock()
|
||||
defer rw.rm.lock.Unlock()
|
||||
close(rw.ch)
|
||||
delete(rw.rm.responseMap, rw.id)
|
||||
}
|
||||
|
||||
type responseManager struct {
|
||||
lock sync.Locker
|
||||
responseMap map[int]*responseWaiter
|
||||
}
|
||||
|
||||
func newResponseManager() *responseManager {
|
||||
rm := new(responseManager)
|
||||
rm.lock = new(sync.Mutex)
|
||||
rm.responseMap = make(map[int]*responseWaiter)
|
||||
return rm
|
||||
}
|
||||
|
||||
func (rm *responseManager) register(id int) ResponseReader {
|
||||
ch := make(chan *Message)
|
||||
rm.lock.Lock()
|
||||
defer rm.lock.Unlock()
|
||||
rm.removeById(id, true)
|
||||
rw := &responseWaiter{
|
||||
rm: rm,
|
||||
id: id,
|
||||
ch: ch,
|
||||
}
|
||||
rm.responseMap[id] = rw
|
||||
return rw
|
||||
}
|
||||
|
||||
func (rm *responseManager) handle(m *Message) bool {
|
||||
rm.lock.Lock()
|
||||
defer rm.lock.Unlock()
|
||||
rw := rm.responseMap[m.MsgID]
|
||||
if rw == nil {
|
||||
return false
|
||||
}
|
||||
rw.ch <- m
|
||||
return true
|
||||
}
|
||||
|
||||
func (rm *responseManager) removeById(id int, klose bool) *responseWaiter{
|
||||
rw := rm.responseMap[id]
|
||||
if rw == nil {
|
||||
return nil
|
||||
}
|
||||
delete(rm.responseMap, id)
|
||||
if klose {
|
||||
close(rw.ch)
|
||||
}
|
||||
return rw
|
||||
}
|
||||
|
@ -0,0 +1,58 @@
|
||||
package ipc
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
rm := newResponseManager()
|
||||
if len(rm.responseMap) != 0 {
|
||||
t.Error("responseMap should be empty")
|
||||
}
|
||||
rm.register(1)
|
||||
rm.register(2)
|
||||
rm.register(1)
|
||||
if len(rm.responseMap) != 2 {
|
||||
t.Errorf("responseMap should have 2 items, not %d", len(rm.responseMap))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveById(t *testing.T) {
|
||||
rm := newResponseManager()
|
||||
rm.register(1)
|
||||
rm.register(2)
|
||||
rm.register(3)
|
||||
|
||||
rm.removeById(2, true)
|
||||
rm.removeById(2, true)
|
||||
|
||||
if len(rm.responseMap) != 2 {
|
||||
t.Errorf("responseMap should have 2 items, not %d", len(rm.responseMap))
|
||||
}
|
||||
rm.removeById(1, true)
|
||||
rm.removeById(3, true)
|
||||
if len(rm.responseMap) != 0 {
|
||||
t.Errorf("responseMap should have 0 items, not %d", len(rm.responseMap))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandle(t *testing.T) {
|
||||
m := new(Message)
|
||||
rm := newResponseManager()
|
||||
rr := rm.register(1)
|
||||
rm.register(2)
|
||||
m.MsgID = 3
|
||||
if rm.handle(m) {
|
||||
t.Errorf("handle() should have returned false")
|
||||
}
|
||||
go func() {
|
||||
<-rr.Chan()
|
||||
}()
|
||||
m.MsgID = 1
|
||||
if !rm.handle(m) {
|
||||
t.Errorf("handle() should have returned true")
|
||||
}
|
||||
if len(rm.responseMap) != 2 {
|
||||
t.Errorf("responseMap should have 2 items after handling message")
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package ipc
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func setPassCred(c net.Conn) error {
|
||||
fd := reflectFD(c)
|
||||
return syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_PASSCRED, 1)
|
||||
}
|
||||
|
||||
func reflectFD(c net.Conn) int {
|
||||
sysfd := extractField(c, "fd", "sysfd")
|
||||
return int(sysfd.Int())
|
||||
}
|
||||
|
||||
func extractField(ob interface{}, fieldNames ...string) reflect.Value {
|
||||
v := reflect.Indirect(reflect.ValueOf(ob))
|
||||
for _, fn := range fieldNames {
|
||||
field := v.FieldByName(fn)
|
||||
v = reflect.Indirect(field)
|
||||
}
|
||||
return v
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
package daemon
|
||||
import (
|
||||
"github.com/subgraph/oz/ipc"
|
||||
"errors"
|
||||
"strconv"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func clientConnect() (*ipc.MsgConn, error) {
|
||||
c := ipc.NewMsgConn(messageFactory, SocketName)
|
||||
if err := c.Connect(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func clientSend(msg interface{}) (*ipc.Message, error) {
|
||||
c,err := clientConnect()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rr, err := c.ExchangeMsg(msg)
|
||||
resp := <- rr.Chan()
|
||||
rr.Done()
|
||||
c.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp,nil
|
||||
}
|
||||
|
||||
func ListProfiles() ([]Profile, error) {
|
||||
resp,err := clientSend(new(ListProfilesMsg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body,ok := resp.Body.(*ListProfilesResp)
|
||||
if !ok {
|
||||
return nil, errors.New("ListProfiles response was not expected type")
|
||||
}
|
||||
return body.Profiles, nil
|
||||
}
|
||||
|
||||
func ListSandboxes() ([]SandboxInfo, error) {
|
||||
resp,err := clientSend(&ListSandboxesMsg{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body,ok := resp.Body.(*ListSandboxesResp)
|
||||
if !ok {
|
||||
return nil, errors.New("ListSandboxes response was not expected type")
|
||||
}
|
||||
return body.Sandboxes, nil
|
||||
}
|
||||
|
||||
func Launch(arg string) error {
|
||||
idx,name,err := parseProfileArg(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp,err := clientSend(&LaunchMsg{
|
||||
Index: idx,
|
||||
Name: name,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch body := resp.Body.(type) {
|
||||
case *ErrorMsg:
|
||||
fmt.Printf("error was %s\n", body.Msg)
|
||||
case *OkMsg:
|
||||
fmt.Println("ok received")
|
||||
default:
|
||||
fmt.Printf("Unexpected message received %+v", body)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Clean(arg string) error {
|
||||
idx,name,err := parseProfileArg(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp,err := clientSend(&CleanMsg{
|
||||
Index: idx,
|
||||
Name: name,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO collapse this logic into a function like clientSend
|
||||
switch body := resp.Body.(type) {
|
||||
case *ErrorMsg:
|
||||
return errors.New(body.Msg)
|
||||
case *OkMsg:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("Unexpected message received %+v", body)
|
||||
}
|
||||
}
|
||||
|
||||
func parseProfileArg(arg string) (int, string, error) {
|
||||
if len(arg) == 0 {
|
||||
return 0, "", errors.New("profile argument needed")
|
||||
}
|
||||
if n,err := strconv.Atoi(arg); err == nil {
|
||||
return n, "", nil
|
||||
}
|
||||
return 0, arg, nil
|
||||
}
|
||||
|
||||
func Logs(count int, follow bool) (chan string, error) {
|
||||
c,err := clientConnect()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rr,err := c.ExchangeMsg(&LogsMsg{Count: count, Follow: follow})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make(chan string)
|
||||
go dumpLogs(out, rr)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func dumpLogs(out chan<- string, rr ipc.ResponseReader) {
|
||||
for resp := range rr.Chan() {
|
||||
switch body := resp.Body.(type) {
|
||||
case *OkMsg:
|
||||
rr.Done()
|
||||
close(out)
|
||||
return
|
||||
case *LogData:
|
||||
for _, ll := range body.Lines {
|
||||
out <- ll
|
||||
}
|
||||
default:
|
||||
out <- fmt.Sprintf("Unexpected response type (%T)", body)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package daemon
|
||||
|
||||
|
||||
type Config struct {
|
||||
profileDir string `json:"profile_dir"`
|
||||
}
|
@ -0,0 +1,158 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/op/go-logging"
|
||||
"github.com/subgraph/oz"
|
||||
"github.com/subgraph/oz/ipc"
|
||||
"syscall"
|
||||
"github.com/subgraph/oz/fs"
|
||||
)
|
||||
|
||||
type daemonState struct {
|
||||
log *logging.Logger
|
||||
profiles oz.Profiles
|
||||
sandboxes []*Sandbox
|
||||
nextSboxId int
|
||||
memBackend *logging.ChannelMemoryBackend
|
||||
backends []logging.Backend
|
||||
}
|
||||
|
||||
func Main() {
|
||||
d := initialize()
|
||||
|
||||
err := runServer(
|
||||
d.handlePing,
|
||||
d.handleListProfiles,
|
||||
d.handleLaunch,
|
||||
d.handleListSandboxes,
|
||||
d.handleClean,
|
||||
d.handleLogs,
|
||||
)
|
||||
if err != nil {
|
||||
d.log.Warning("Error running server: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func initialize() *daemonState {
|
||||
d := &daemonState{}
|
||||
d.initializeLogging()
|
||||
ps,err := oz.LoadProfiles("/var/lib/oz/cells.d")
|
||||
if err != nil {
|
||||
d.log.Fatalf("Failed to load profiles: %v", err)
|
||||
}
|
||||
d.Debug("%d profiles loaded", len(ps))
|
||||
d.profiles = ps
|
||||
oz.ReapChildProcs(d.log, d.handleChildExit)
|
||||
d.nextSboxId = 1
|
||||
return d
|
||||
}
|
||||
|
||||
|
||||
func (d *daemonState) handleChildExit(pid int, wstatus syscall.WaitStatus) {
|
||||
d.Debug("Child process pid=%d exited with status %d", pid, wstatus.ExitStatus())
|
||||
for _,sbox := range d.sandboxes {
|
||||
if sbox.init.Process.Pid == pid {
|
||||
sbox.fs.Cleanup()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runServer(args ...interface{}) error {
|
||||
serv := ipc.NewMsgConn(messageFactory, SocketName)
|
||||
if err := serv.AddHandlers(args...); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := serv.Listen(); err != nil {
|
||||
return err
|
||||
}
|
||||
return serv.Run()
|
||||
}
|
||||
|
||||
func (d * daemonState) handlePing(msg *PingMsg, m *ipc.Message) error {
|
||||
d.Debug("received ping with data [%s]", msg.Data)
|
||||
return m.Respond(&PingMsg{msg.Data})
|
||||
}
|
||||
|
||||
func (d * daemonState) handleListProfiles(msg *ListProfilesMsg, m *ipc.Message) error {
|
||||
r := new(ListProfilesResp)
|
||||
index := 1
|
||||
for _,p := range d.profiles {
|
||||
r.Profiles = append(r.Profiles, Profile{Index: index, Name: p.Name, Path: p.Path})
|
||||
index += 1
|
||||
}
|
||||
return m.Respond(r)
|
||||
}
|
||||
|
||||
func (d *daemonState) handleLaunch(msg *LaunchMsg, m *ipc.Message) error {
|
||||
d.Debug("Launch message received: %+v", msg)
|
||||
p,err := d.getProfileByIdxOrName(msg.Index, msg.Name)
|
||||
if err != nil {
|
||||
return m.Respond(&ErrorMsg{err.Error()})
|
||||
}
|
||||
d.Debug("Would launch %s", p.Name)
|
||||
|
||||
_,err = d.launch(p)
|
||||
if err != nil {
|
||||
d.Warning("launch of %s failed: %v", p.Name, err)
|
||||
return m.Respond(&ErrorMsg{err.Error()})
|
||||
}
|
||||
return m.Respond(&OkMsg{})
|
||||
}
|
||||
|
||||
func (d *daemonState) getProfileByIdxOrName(index int, name string) (*oz.Profile, error) {
|
||||
if len(name) == 0 {
|
||||
if index < 1 || index > len(d.profiles) {
|
||||
return nil, fmt.Errorf("not a valid profile index (%d)", index)
|
||||
}
|
||||
return d.profiles[index-1], nil
|
||||
}
|
||||
|
||||
for _,p := range d.profiles {
|
||||
if p.Name == name {
|
||||
return p,nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("could not find profile name '%s'", name)
|
||||
}
|
||||
|
||||
func (d *daemonState) handleListSandboxes(list *ListSandboxesMsg, msg *ipc.Message) error {
|
||||
r := new(ListSandboxesResp)
|
||||
for _, sb := range d.sandboxes {
|
||||
r.Sandboxes = append(r.Sandboxes, SandboxInfo{Id: sb.id, Address: sb.addr, Profile: sb.profile.Name})
|
||||
}
|
||||
return msg.Respond(r)
|
||||
}
|
||||
|
||||
func (d *daemonState) handleClean(clean *CleanMsg, msg *ipc.Message) error {
|
||||
p,err := d.getProfileByIdxOrName(clean.Index, clean.Name)
|
||||
if err != nil {
|
||||
return msg.Respond(&ErrorMsg{err.Error()})
|
||||
}
|
||||
for _, sb := range d.sandboxes {
|
||||
if sb.profile.Name == p.Name {
|
||||
errmsg := fmt.Sprintf("Cannot clean profile '%s' because there are sandboxes running for this profile", p.Name)
|
||||
return msg.Respond(&ErrorMsg{errmsg})
|
||||
}
|
||||
}
|
||||
fs := fs.NewFromProfile(p, d.log)
|
||||
if err := fs.Cleanup(); err != nil {
|
||||
return msg.Respond(&ErrorMsg{err.Error()})
|
||||
}
|
||||
return msg.Respond(&OkMsg{})
|
||||
}
|
||||
|
||||
func (d *daemonState) handleLogs(logs *LogsMsg, msg *ipc.Message) error {
|
||||
for n := d.memBackend.Head(); n != nil; n = n.Next() {
|
||||
s := n.Record.Formatted(0)
|
||||
msg.Respond(&LogData{Lines: []string{s}})
|
||||
}
|
||||
if logs.Follow {
|
||||
d.followLogs(msg)
|
||||
return nil
|
||||
}
|
||||
msg.Respond(&OkMsg{})
|
||||
return nil
|
||||
}
|
||||
|
@ -0,0 +1,128 @@
|
||||
package daemon
|
||||
import (
|
||||
"github.com/subgraph/oz"
|
||||
"github.com/subgraph/oz/fs"
|
||||
"os/exec"
|
||||
"github.com/subgraph/oz/ipc"
|
||||
"syscall"
|
||||
"fmt"
|
||||
"io"
|
||||
"bufio"
|
||||
)
|
||||
|
||||
const initPath = "/usr/local/bin/oz-init"
|
||||
|
||||
|
||||
type Sandbox struct {
|
||||
daemon *daemonState
|
||||
id int
|
||||
profile *oz.Profile
|
||||
init *exec.Cmd
|
||||
fs *fs.Filesystem
|
||||
stderr io.ReadCloser
|
||||
addr string
|
||||
}
|
||||
|
||||
/*
|
||||
func findSandbox(id int) *Sandbox {
|
||||
for _, sb := range sandboxes {
|
||||
if sb.id == id {
|
||||
return sb
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
*/
|
||||
const initCloneFlags = syscall.CLONE_NEWNS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWUTS
|
||||
|
||||
func createInitCommand(addr, name, chroot string) *exec.Cmd {
|
||||
cmd := exec.Command(initPath)
|
||||
cmd.Dir = "/"
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Chroot: chroot,
|
||||
Cloneflags: initCloneFlags,
|
||||
}
|
||||
cmd.Env = []string{
|
||||
"INIT_ADDRESS="+addr,
|
||||
"INIT_PROFILE="+name,
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (d *daemonState) launch(p *oz.Profile) (*Sandbox, error) {
|
||||
fs := fs.NewFromProfile(p, d.log)
|
||||
if err := fs.Setup(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addr,err := ipc.CreateRandomAddress("@oz-init-")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd := createInitCommand(addr, p.Name, fs.Root())
|
||||
pp,err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
fs.Cleanup()
|
||||
return nil, fmt.Errorf("error creating stderr pipe for init process: %v", err)
|
||||
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
fs.Cleanup()
|
||||
return nil, err
|
||||
}
|
||||
sbox := &Sandbox{
|
||||
daemon: d,
|
||||
id: d.nextSboxId,
|
||||
profile: p,
|
||||
init: cmd,
|
||||
fs: fs,
|
||||
addr: addr,
|
||||
stderr: pp,
|
||||
}
|
||||
go sbox.logMessages()
|
||||
d.nextSboxId += 1
|
||||
d.sandboxes = append(d.sandboxes, sbox)
|
||||
return sbox,nil
|
||||
}
|
||||
|
||||
func (sbox *Sandbox) logMessages() {
|
||||
scanner := bufio.NewScanner(sbox.stderr)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if len(line) > 1 {
|
||||
sbox.logLine(line)
|
||||
}
|
||||
}
|
||||
sbox.stderr.Close()
|
||||
}
|
||||
|
||||
func (sbox *Sandbox) logLine(line string) {
|
||||
if len(line) < 2 {
|
||||
return
|
||||
}
|
||||
f := sbox.getLogFunc(line[0])
|
||||
msg := line[2:]
|
||||
if f != nil {
|
||||
f("[%s] %s", sbox.profile.Name, msg)
|
||||
} else {
|
||||
sbox.daemon.log.Info("[%s] %s", sbox.profile.Name, line)
|
||||
}
|
||||
}
|
||||
|
||||
func (sbox *Sandbox) getLogFunc(c byte) func(string, ...interface{}) {
|
||||
log := sbox.daemon.log
|
||||
switch(c) {
|
||||
case 'D':
|
||||
return log.Debug
|
||||
case 'I':
|
||||
return log.Info
|
||||
case 'N':
|
||||
return log.Notice
|
||||
case 'W':
|
||||
return log.Warning
|
||||
case 'E':
|
||||
return log.Error
|
||||
case 'C':
|
||||
return log.Critical
|
||||
}
|
||||
return nil
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package daemon
|
||||
import (
|
||||
"github.com/op/go-logging"
|
||||
"log"
|
||||
"os"
|
||||
"github.com/subgraph/oz/ipc"
|
||||
)
|
||||
|
||||
|
||||
func (d *daemonState) Debug(format string, args ...interface{}) {
|
||||
d.log.Debug(format, args...)
|
||||
}
|
||||
func (d *daemonState) Info(format string, args ...interface{}) {
|
||||
d.log.Info(format, args...)
|
||||
}
|
||||
func (d *daemonState) Notice(format string, args ...interface{}) {
|
||||
d.log.Notice(format, args...)
|
||||
}
|
||||
func (d *daemonState) Warning(format string, args ...interface{}) {
|
||||
d.log.Warning(format, args...)
|
||||
}
|
||||
func (d *daemonState) Error(format string, args ...interface{}) {
|
||||
d.log.Error(format, args...)
|
||||
}
|
||||
func (d *daemonState) Critical(format string, args ...interface{}) {
|
||||
d.log.Critical(format, args...)
|
||||
}
|
||||
|
||||
func (d *daemonState) initializeLogging() {
|
||||
d.log = logging.MustGetLogger("oz")
|
||||
be := logging.NewChannelMemoryBackend(100)
|
||||
fbe := logging.NewBackendFormatter(be, format)
|
||||
d.memBackend = be
|
||||
stderr := logging.NewLogBackend(os.Stderr, "", log.LstdFlags)
|
||||
d.backends = []logging.Backend{
|
||||
stderr,
|
||||
fbe,
|
||||
}
|
||||
d.installBackends()
|
||||
}
|
||||
var format = logging.MustStringFormatter(
|
||||
"%{color}%{time:15:04:05} â–¶ %{level:.4s} %{id:03x}%{color:reset} %{message}",
|
||||
)
|
||||
func (d *daemonState) addBackend(be logging.Backend) {
|
||||
d.backends = append(d.backends, be)
|
||||
d.installBackends()
|
||||
}
|
||||
|
||||
func (d *daemonState) removeBackend(be logging.Backend) {
|
||||
newBackends := []logging.Backend{}
|
||||
for _,b := range d.backends {
|
||||
if b != be {
|
||||
newBackends = append(newBackends, b)
|
||||
}
|
||||
}
|
||||
d.backends = newBackends
|
||||
d.installBackends()
|
||||
}
|
||||
|
||||
func (d *daemonState) installBackends() {
|
||||
if len(d.backends) == 1 {
|
||||
d.log.SetBackend(logging.AddModuleLevel(d.backends[0]))
|
||||
return
|
||||
}
|
||||
d.log.SetBackend(logging.MultiLogger(d.backends...))
|
||||
}
|
||||
|
||||
type logFollower struct {
|
||||
daemon *daemonState
|
||||
wrapper logging.Backend
|
||||
m *ipc.Message
|
||||
}
|
||||
|
||||
func (lf *logFollower) Log(level logging.Level, calldepth int, rec *logging.Record) error {
|
||||
s := rec.Formatted(calldepth)
|
||||
if err := lf.m.Respond(&LogData{[]string{s}}); err != nil {
|
||||
lf.remove()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lf *logFollower) remove() {
|
||||
lf.daemon.removeBackend(lf.wrapper)
|
||||
}
|
||||
|
||||
func (d *daemonState) followLogs(m *ipc.Message) {
|
||||
be := &logFollower{m:m, daemon: d}
|
||||
be.wrapper = logging.NewBackendFormatter(be, format)
|
||||
d.addBackend(be.wrapper)
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
package daemon
|
||||
import "github.com/subgraph/oz/ipc"
|
||||
|
||||
const SocketName = "@oz-control"
|
||||
|
||||
type OkMsg struct {
|
||||
_ string "Ok"
|
||||
}
|
||||
|
||||
type ErrorMsg struct {
|
||||
Msg string "Error"
|
||||
}
|
||||
|
||||
type PingMsg struct {
|
||||
Data string "Ping"
|
||||
}
|
||||
|
||||
type ListProfilesMsg struct {
|
||||
_ string "ListProfiles"
|
||||
}
|
||||
|
||||
type Profile struct {
|
||||
Index int
|
||||
Name string
|
||||
Path string
|
||||
}
|
||||
|
||||
type ListProfilesResp struct {
|
||||
Profiles []Profile "ListProfilesResp"
|
||||
}
|
||||
|
||||
type LaunchMsg struct {
|
||||
Index int "Launch"
|
||||
Name string
|
||||
}
|
||||
|
||||
type ListSandboxesMsg struct {
|
||||
_ string "ListSandboxes"
|
||||
}
|
||||
|
||||
type SandboxInfo struct {
|
||||
Id int
|
||||
Address string
|
||||
Profile string
|
||||
}
|
||||
|
||||
type ListSandboxesResp struct {
|
||||
Sandboxes []SandboxInfo "ListSandboxesResp"
|
||||
}
|
||||
|
||||
type CleanMsg struct {
|
||||
Index int "Clean"
|
||||
Name string
|
||||
}
|
||||
|
||||
type LogsMsg struct {
|
||||
Count int "Logs"
|
||||
Follow bool
|
||||
}
|
||||
|
||||
type LogData struct {
|
||||
Lines []string "LogData"
|
||||
}
|
||||
|
||||
var messageFactory = ipc.NewMsgFactory(
|
||||
new(PingMsg),
|
||||
new(OkMsg),
|
||||
new(ErrorMsg),
|
||||
new(ListProfilesMsg),
|
||||
new(ListProfilesResp),
|
||||
new(LaunchMsg),
|
||||
new(ListSandboxesMsg),
|
||||
new(ListSandboxesResp),
|
||||
new(CleanMsg),
|
||||
new(LogsMsg),
|
||||
new(LogData),
|
||||
)
|
||||
|
@ -0,0 +1 @@
|
||||
package daemon
|
@ -0,0 +1,72 @@
|
||||
package ozinit
|
||||
import (
|
||||
"github.com/subgraph/oz/ipc"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func clientConnect(addr string) (*ipc.MsgConn, error) {
|
||||
c := ipc.NewMsgConn(messageFactory, addr)
|
||||
if err := c.Connect(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func clientSend(addr string, msg interface{}) (*ipc.Message, error) {
|
||||
c,err := clientConnect(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rr, err := c.ExchangeMsg(msg)
|
||||
resp := <- rr.Chan()
|
||||
rr.Done()
|
||||
|
||||
c.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func Ping(addr string) error {
|
||||
resp,err := clientSend(addr, new(PingMsg))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch body := resp.Body.(type) {
|
||||
case *PingMsg:
|
||||
return nil
|
||||
case *ErrorMsg:
|
||||
return errors.New(body.Msg)
|
||||
default:
|
||||
return fmt.Errorf("Unexpected message received: %+v", body)
|
||||
}
|
||||
}
|
||||
|
||||
func RunShell(addr, term string) (int, error) {
|
||||
c,err := clientConnect(addr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
rr,err := c.ExchangeMsg(&RunShellMsg{Term: term})
|
||||
resp := <- rr.Chan()
|
||||
rr.Done()
|
||||
c.Close()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch body := resp.Body.(type) {
|
||||
case *ErrorMsg:
|
||||
return 0,errors.New(body.Msg)
|
||||
case *OkMsg:
|
||||
if len(resp.Fds) == 0 {
|
||||
return 0, errors.New("RunShell message returned Ok, but no file descriptor received")
|
||||
}
|
||||
return resp.Fds[0], nil
|
||||
default:
|
||||
return 0, fmt.Errorf("Unexpected message type received: %+v", body)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,160 @@
|
||||
package ozinit
|
||||
|
||||
import (
|
||||
"os"
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/subgraph/oz"
|
||||
"github.com/subgraph/oz/fs"
|
||||
"github.com/subgraph/oz/ipc"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
"github.com/op/go-logging"
|
||||
"github.com/kr/pty"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const profileDirectory = "/var/lib/oz/cells.d"
|
||||
|
||||
var log = createLogger()
|
||||
|
||||
func createLogger() *logging.Logger {
|
||||
l := logging.MustGetLogger("oz-init")
|
||||
be := logging.NewLogBackend(os.Stderr, "", 0)
|
||||
f := logging.MustStringFormatter("%{level:.1s} %{message}")
|
||||
fbe := logging.NewBackendFormatter(be,f)
|
||||
logging.SetBackend(fbe)
|
||||
return l
|
||||
}
|
||||
|
||||
var allowRootShell = false
|
||||
var profileName = "none"
|
||||
|
||||
func Main() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "oz-init"
|
||||
app.Action = runInit
|
||||
app.Flags = []cli.Flag {
|
||||
cli.StringFlag {
|
||||
Name: "address",
|
||||
Usage: "unix socket address to listen for commands on",
|
||||
EnvVar: "INIT_ADDRESS",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "profile",
|
||||
Usage: "name of profile to launch",
|
||||
EnvVar: "INIT_PROFILE",
|
||||
},
|
||||
|
||||
}
|
||||
app.Run(os.Args)
|
||||
}
|
||||
|
||||
func runInit(c *cli.Context) {
|
||||
address := c.String("address")
|
||||
profile := c.String("profile")
|
||||
if address == "" {
|
||||
log.Error("Error: missing required 'address' argument")
|
||||
os.Exit(1)
|
||||
}
|
||||
if profile == "" {
|
||||
log.Error("Error: missing required 'profile' argument")
|
||||
os.Exit(1)
|
||||
}
|
||||
profileName = profile
|
||||
log.Info("Starting oz-init for profile: %s", profile)
|
||||
log.Info("Socket address: %s", address)
|
||||
p,err := loadProfile(profile)
|
||||
if err != nil {
|
||||
log.Error("Could not load profile %s: %v", profile, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fs := fs.NewFromProfile(p, log)
|
||||
if err := fs.OzInit(); err != nil {
|
||||
log.Error("Error: setting up filesystem failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
oz.ReapChildProcs(log, handleChildExit)
|
||||
|
||||
serv := ipc.NewMsgConn(messageFactory, address)
|
||||
serv.AddHandlers(
|
||||
handlePing,
|
||||
handleRunShell,
|
||||
)
|
||||
serv.Listen()
|
||||
serv.Run()
|
||||
log.Info("oz-init exiting...")
|
||||
}
|
||||
|
||||
func loadProfile(name string) (*oz.Profile, error) {
|
||||
ps,err := oz.LoadProfiles(profileDirectory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _,p := range ps {
|
||||
if name == p.Name {
|
||||
return p, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("no profile named '%s'", name)
|
||||
}
|
||||
|
||||
func handlePing(ping *PingMsg, msg *ipc.Message) error {
|
||||
return msg.Respond(&PingMsg{Data: ping.Data})
|
||||
}
|
||||
|
||||
func handleRunShell(rs *RunShellMsg, msg *ipc.Message) error {
|
||||
if msg.Ucred == nil {
|
||||
return msg.Respond(&ErrorMsg{"No credentials received for RunShell command"})
|
||||
}
|
||||
if msg.Ucred.Uid == 0 || msg.Ucred.Gid == 0 && !allowRootShell {
|
||||
return msg.Respond(&ErrorMsg{"Cannot open shell because allowRootShell is disabled"})
|
||||
}
|
||||
log.Info("Starting shell with uid = %d, gid = %d", msg.Ucred.Uid, msg.Ucred.Gid)
|
||||
cmd := exec.Command("/bin/sh", "-i")
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
cmd.SysProcAttr.Credential = &syscall.Credential{
|
||||
Uid: msg.Ucred.Uid,
|
||||
Gid: msg.Ucred.Gid,
|
||||
}
|
||||
if rs.Term != "" {
|
||||
cmd.Env = append(cmd.Env, "TERM="+rs.Term)
|
||||
}
|
||||
cmd.Env = append(cmd.Env, "PATH=/usr/bin:/bin")
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("PS1=[%s] $ ", profileName))
|
||||
log.Info("Executing shell...")
|
||||
f,err := ptyStart(cmd)
|
||||
defer f.Close()
|
||||
if err != nil {
|
||||
return msg.Respond(&ErrorMsg{err.Error()})
|
||||
}
|
||||
err = msg.Respond(&OkMsg{}, int(f.Fd()))
|
||||
return err
|
||||
}
|
||||
|
||||
func ptyStart(c *exec.Cmd) (ptty *os.File, err error) {
|
||||
ptty,tty, err := pty.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tty.Close()
|
||||
c.Stdin = tty
|
||||
c.Stdout = tty
|
||||
c.Stderr = tty
|
||||
if c.SysProcAttr == nil {
|
||||
c.SysProcAttr = &syscall.SysProcAttr{}
|
||||
}
|
||||
c.SysProcAttr.Setctty = true
|
||||
c.SysProcAttr.Setsid = true
|
||||
if err := c.Start(); err != nil {
|
||||
ptty.Close()
|
||||
return nil, err
|
||||
}
|
||||
return ptty, nil
|
||||
}
|
||||
|
||||
func handleChildExit(pid int, wstatus syscall.WaitStatus) {
|
||||
log.Debug("Child process pid=%d exited with status %d", pid, wstatus.ExitStatus())
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
package ozinit
|
||||
import "github.com/subgraph/oz/ipc"
|
||||
|
||||
type OkMsg struct {
|
||||
_ string "Ok"
|
||||
}
|
||||
|
||||
type ErrorMsg struct {
|
||||
Msg string "Error"
|
||||
}
|
||||
|
||||
type PingMsg struct {
|
||||
Data string "Ping"
|
||||
}
|
||||
|
||||
type RunShellMsg struct {
|
||||
Term string "RunShell"
|
||||
}
|
||||
|
||||
var messageFactory = ipc.NewMsgFactory(
|
||||
new(OkMsg),
|
||||
new(ErrorMsg),
|
||||
new(PingMsg),
|
||||
new(RunShellMsg),
|
||||
)
|
||||
|
@ -0,0 +1,168 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/codegangsta/cli"
|
||||
"os"
|
||||
"github.com/subgraph/oz/oz-daemon"
|
||||
"strconv"
|
||||
"io"
|
||||
"github.com/subgraph/oz/oz-init"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
|
||||
app.Name = "oz"
|
||||
app.Usage = "command line interface to Oz sandboxes"
|
||||
app.Author = "Subgraph"
|
||||
app.Email = "info@subgraph.com"
|
||||
app.Commands = []cli.Command {
|
||||
{
|
||||
Name: "profiles",
|
||||
Usage: "list available application profiles",
|
||||
Action: handleProfiles,
|
||||
},
|
||||
{
|
||||
Name: "launch",
|
||||
Usage: "launch an application profile",
|
||||
Action: handleLaunch,
|
||||
},
|
||||
{
|
||||
Name: "list",
|
||||
Usage: "list running sandboxes",
|
||||
Action: handleList,
|
||||
},
|
||||
{
|
||||
Name: "shell",
|
||||
Usage: "start a shell in a running container",
|
||||
Action: handleShell,
|
||||
},
|
||||
{
|
||||
Name: "clean",
|
||||
Action: handleClean,
|
||||
|
||||
},
|
||||
{
|
||||
Name: "logs",
|
||||
Action: handleLogs,
|
||||
Flags: []cli.Flag {
|
||||
cli.BoolFlag{
|
||||
Name: "f",
|
||||
},
|
||||
|
||||
},
|
||||
},
|
||||
}
|
||||
app.Run(os.Args)
|
||||
}
|
||||
|
||||
func handleProfiles(c *cli.Context) {
|
||||
ps,err := daemon.ListProfiles()
|
||||
if err != nil {
|
||||
fmt.Printf("Error listing profiles: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
for i,p := range ps {
|
||||
fmt.Printf("%2d) %-30s %s\n", i+1, p.Name, p.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func handleLaunch(c *cli.Context) {
|
||||
if len(c.Args()) == 0 {
|
||||
fmt.Println("Argument needed to launch command")
|
||||
os.Exit(1)
|
||||
}
|
||||
err := daemon.Launch(c.Args()[0])
|
||||
if err != nil {
|
||||
fmt.Printf("launch command failed: %v\n", err)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func handleList(c *cli.Context) {
|
||||
sboxes,err := daemon.ListSandboxes()
|
||||
if err != nil {
|
||||
fmt.Printf("Error listing running containers: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if len(sboxes) == 0 {
|
||||
fmt.Println("No running containers")
|
||||
return
|
||||
}
|
||||
for _, sb := range sboxes {
|
||||
fmt.Printf("%2d) %s\n", sb.Id, sb.Profile)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func handleShell(c *cli.Context) {
|
||||
if len(c.Args()) == 0 {
|
||||
fmt.Println("Sandbox id argument needed")
|
||||
os.Exit(1)
|
||||
}
|
||||
id,err := strconv.Atoi(c.Args()[0])
|
||||
if err != nil {
|
||||
fmt.Println("Sandbox id argument must be an integer")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
sb,err := getSandboxById(id)
|
||||
if err != nil {
|
||||
fmt.Printf("Error retrieving sandbox list: %v\n", err)
|
||||
}
|
||||
if sb == nil {
|
||||
fmt.Printf("No sandbox found with id = %d\n", id)
|
||||
}
|
||||
term := os.Getenv("TERM")
|
||||
fd,err := ozinit.RunShell(sb.Address, term)
|
||||
if err != nil {
|
||||
fmt.Printf("start shell command failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("Entering interactive shell?\n")
|
||||
st,err := SetRawTerminal(0)
|
||||
HandleResize(fd)
|
||||
f := os.NewFile(uintptr(fd), "")
|
||||
go io.Copy(f, os.Stdin)
|
||||
io.Copy(os.Stdout, f)
|
||||
RestoreTerminal(0, st)
|
||||
fmt.Println("done..")
|
||||
}
|
||||
|
||||
|
||||
func getSandboxById(id int) (*daemon.SandboxInfo, error) {
|
||||
sboxes,err := daemon.ListSandboxes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, sb := range sboxes {
|
||||
if id == sb.Id {
|
||||
return &sb,nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func handleClean(c *cli.Context) {
|
||||
if len(c.Args()) == 0 {
|
||||
fmt.Println("Need a profile to clean")
|
||||
os.Exit(1)
|
||||
}
|
||||
err := daemon.Clean(c.Args()[0])
|
||||
if err != nil {
|
||||
fmt.Println("Clean failed:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func handleLogs(c *cli.Context) {
|
||||
follow := c.Bool("f")
|
||||
ch,err := daemon.Logs(0,follow)
|
||||
if err != nil {
|
||||
fmt.Println("Logs failed", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
for ll := range ch {
|
||||
fmt.Println(ll)
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
package main
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
"os"
|
||||
"os/signal"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type winsize struct {
|
||||
Height uint16
|
||||
Width uint16
|
||||
x uint16
|
||||
y uint16
|
||||
}
|
||||
type State struct {
|
||||
termios Termios
|
||||
}
|
||||
|
||||
const (
|
||||
getTermios = syscall.TCGETS
|
||||
setTermios = syscall.TCSETS
|
||||
)
|
||||
|
||||
type Termios struct {
|
||||
Iflag uint32
|
||||
Oflag uint32
|
||||
Cflag uint32
|
||||
Lflag uint32
|
||||
Cc [20]byte
|
||||
Ispeed uint32
|
||||
Ospeed uint32
|
||||
}
|
||||
|
||||
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||
// mode and returns the previous state of the terminal so that it can be
|
||||
// restored.
|
||||
func MakeRaw(fd uintptr) (*State, error) {
|
||||
var oldState State
|
||||
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, getTermios, uintptr(unsafe.Pointer(&oldState.termios))); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newState := oldState.termios
|
||||
|
||||
newState.Iflag &^= (syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON)
|
||||
newState.Oflag &^= syscall.OPOST
|
||||
newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN)
|
||||
newState.Cflag &^= (syscall.CSIZE | syscall.PARENB)
|
||||
newState.Cflag |= syscall.CS8
|
||||
|
||||
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(&newState))); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
return &oldState, nil
|
||||
}
|
||||
|
||||
// Restore restores the terminal connected to the given file descriptor to a
|
||||
// previous state.
|
||||
func RestoreTerminal(fd uintptr, state *State) error {
|
||||
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&state.termios)))
|
||||
return err
|
||||
}
|
||||
|
||||
func SetRawTerminal(fd uintptr) (*State, error) {
|
||||
oldState, err := MakeRaw(fd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
go func() {
|
||||
_ = <-c
|
||||
RestoreTerminal(fd, oldState)
|
||||
os.Exit(0)
|
||||
}()
|
||||
return oldState, err
|
||||
}
|
||||
|
||||
func GetWinsize(fd uintptr) (*winsize, syscall.Errno) {
|
||||
ws := &winsize{}
|
||||
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(ws)))
|
||||
return ws, err
|
||||
}
|
||||
|
||||
func SetWinsize(fd uintptr, ws *winsize) syscall.Errno {
|
||||
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(ws)))
|
||||
return err
|
||||
}
|
||||
|
||||
func HandleResize(fd int) {
|
||||
sigs := make(chan os.Signal)
|
||||
signal.Notify(sigs, syscall.SIGWINCH)
|
||||
go func() {
|
||||
for {
|
||||
<-sigs
|
||||
handleSIGWINCH(fd)
|
||||
}
|
||||
}()
|
||||
handleSIGWINCH(fd)
|
||||
}
|
||||
|
||||
func handleSIGWINCH(fd int) {
|
||||
ws,errno := GetWinsize(0)
|
||||
if errno != 0 {
|
||||
fmt.Printf("error reading winsize: %v\n", errno.Error())
|
||||
return
|
||||
}
|
||||
if errno := SetWinsize(uintptr(fd), ws); errno != 0 {
|
||||
fmt.Printf("error setting winsize: %v", errno.Error())
|
||||
}
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
package oz
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
)
|
||||
|
||||
type Profile struct {
|
||||
// Name of this profile
|
||||
Name string
|
||||
// Path to binary to launch
|
||||
Path string
|
||||
// Optional path of binary to watch for watchdog purposes if different than Path
|
||||
Watchdog string
|
||||
// Optional wrapper binary to use when launching command (ex: tsocks)
|
||||
Wrapper string
|
||||
// If true launch one container per instance, otherwise run all instances in same container
|
||||
Multi bool
|
||||
// Disable mounting of sys and proc inside the container
|
||||
NoSysProc bool
|
||||
// Disable bind mounting of default directories (etc,usr,bin,lib,lib64)
|
||||
// Also disables default blacklist items (/sbin, /usr/sbin, /usr/bin/sudo)
|
||||
// Normally not used
|
||||
NoDefaults bool
|
||||
// List of paths to bind mount inside jail
|
||||
Whitelist []WhitelistItem
|
||||
// List of paths to blacklist inside jail
|
||||
Blacklist []BlacklistItem
|
||||
// Optional XServer config
|
||||
XServer XServerConf
|
||||
// List of environment variables
|
||||
Environment []EnvVar
|
||||
}
|
||||
|
||||
type XServerConf struct {
|
||||
Enabled bool
|
||||
TrayIcon string `json:"tray_icon"`
|
||||
WindowIcon string `json:"window_icon"`
|
||||
EnableTray bool `json:"enable_tray"`
|
||||
UseDBUS bool `json:"use_dbus"`
|
||||
UsePulseAudio bool `json:"use_pulse_audio"`
|
||||
DisableClipboard bool `json:"disable_clipboard"`
|
||||
DisableAudio bool `json:"disable_audio"`
|
||||
WorkDir string `json:"work_dir"`
|
||||
}
|
||||
|
||||
type WhitelistItem struct {
|
||||
Path string
|
||||
ReadOnly bool
|
||||
}
|
||||
|
||||
type BlacklistItem struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
type EnvVar struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
const defaultProfileDirectory = "/var/lib/oz/cells.d"
|
||||
|
||||
var loadedProfiles []*Profile
|
||||
|
||||
type Profiles []*Profile
|
||||
|
||||
func (ps Profiles) GetProfileByName(name string) (*Profile,error) {
|
||||
if loadedProfiles == nil {
|
||||
ps ,err := LoadProfiles(defaultProfileDirectory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
loadedProfiles = ps
|
||||
}
|
||||
|
||||
for _,p := range loadedProfiles {
|
||||
if p.Name == name {
|
||||
return p,nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func LoadProfiles(dir string) (Profiles, error) {
|
||||
fs, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ps := []*Profile{}
|
||||
for _, f := range fs {
|
||||
if !f.IsDir() {
|
||||
name := path.Join(dir, f.Name())
|
||||
p, err := loadProfileFile(name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error loading '%s': %v", f.Name(), err)
|
||||
}
|
||||
ps = append(ps, p)
|
||||
}
|
||||
}
|
||||
return ps, nil
|
||||
}
|
||||
|
||||
func loadProfileFile(file string) (*Profile, error) {
|
||||
bs, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := new(Profile)
|
||||
if err := json.Unmarshal(bs, p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if p.Name == "" {
|
||||
p.Name = path.Base(p.Path)
|
||||
}
|
||||
return p, nil
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
{
|
||||
"path": "/usr/bin/evince"
|
||||
, "xserver": {
|
||||
"enabled": true
|
||||
, "enable_tray": true
|
||||
, "tray_icon":"/usr/share/icons/hicolor/48x48/apps/evince.png"
|
||||
}
|
||||
, "network":{
|
||||
"nettype":"empty"
|
||||
}
|
||||
, "whitelist": [
|
||||
]
|
||||
, "blacklist": [
|
||||
]
|
||||
, "environment": [
|
||||
]
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
{
|
||||
"path": "/usr/bin/gajim"
|
||||
, "xserver": {
|
||||
"enabled": true
|
||||
, "enable_tray": true
|
||||
, "tray_icon":"/usr/share/icons/gnome-colors-common/scalable/apps/gajim.svg"
|
||||
, "disable_audio":true
|
||||
}
|
||||
, "network":{
|
||||
"nettype":"empty"
|
||||
, "sockets": [
|
||||
{"nettype":"client", "proto":"tcp", "port":9050}
|
||||
, {"nettype":"client", "destination": "xmpp.lalonde.me", "proto":"tcp", "port":5222}
|
||||
, {"nettype":"client", "destination": "xmpp.lalonde.me", "proto":"tcp", "port":5269}
|
||||
, {"nettype":"client", "destination": "xmpp.lalonde.me", "proto":"tcp", "port":5000}
|
||||
, {"nettype":"client", "destination": "xmpp.lalonde.me", "proto":"tcp", "port":5280}
|
||||
, {"nettype":"client", "destination": "xmpp.lalonde.me", "proto":"tcp", "port":5281}
|
||||
]
|
||||
}
|
||||
, "whitelist": [
|
||||
{"source":"/run/resolvconf"}
|
||||
, {"source":"/run/user/${UID}/keyring-*"}
|
||||
, {"source":"${HOME}/.local/share/gajim"}
|
||||
, {"source":"${HOME}/.cache/gajim"}
|
||||
, {"source":"${HOME}/.config/gajim"}
|
||||
, {"source":"${HOME}/.local/share/keyrings"}
|
||||
]
|
||||
, "blacklist": [
|
||||
{"source":"/run/user/${UID}/keyring-*/ssh"}
|
||||
, {"source":"/run/user/${UID}/keyring-*/pkcs11"}
|
||||
, {"source":"/run/user/${UID}/keyring-*/gpg"}
|
||||
]
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
{
|
||||
"path": "/usr/bin/icedove"
|
||||
, "xserver": {
|
||||
"enabled": true
|
||||
, "enable_tray": true
|
||||
, "tray_icon": "/usr/share/icons/hicolor/scalable/apps/icedove.svg"
|
||||
, "disable_audio": true
|
||||
}
|
||||
, "network":{
|
||||
"nettype":"bridge"
|
||||
, "sockets": [
|
||||
{"nettype":"client", "proto":"tcp", "port":9050}
|
||||
]
|
||||
}
|
||||
, "whitelist": [
|
||||
{"source":"/run/resolvconf"}
|
||||
, {"source":"/run/user/${UID}/keyring"}
|
||||
, {"source":"/tmp/gpg-*"}
|
||||
, {"source":"${HOME}/.cache/icedove"}
|
||||
, {"source":"${HOME}/.gnupg"}
|
||||
, {"source":"${HOME}/..thunderbird"}
|
||||
, {"source":"${HOME}/.icedove"}
|
||||
|
||||
, {"source":"${HOME}/.config/gtk-3.0"}
|
||||
, {"source":"${HOME}/.config/gtk-2.0"}
|
||||
]
|
||||
, "_blacklist": [
|
||||
]
|
||||
, "environment": [
|
||||
{"name":"GPG_AGENT_INFO"}
|
||||
, {"name":"GNOME_KEYRING_CONTROL"}
|
||||
, {"name":"GNOME_KEYRING_PID", "value":"1"}
|
||||
]
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
{
|
||||
"path": "/usr/bin/iceweasel"
|
||||
, "xserver": {
|
||||
"enabled": true
|
||||
, "enable_tray": true
|
||||
, "tray_icon":"/usr/share/icons/hicolor/scalable/apps/iceweasel.svg"
|
||||
}
|
||||
, "network":{
|
||||
"nettype":"bridge"
|
||||
, "sockets": [
|
||||
{"nettype":"client", "proto":"tcp", "port":9050}
|
||||
]
|
||||
}
|
||||
, "whitelist": [
|
||||
{"source":"/run/resolvconf"}
|
||||
, {"source":"${HOME}/.mozilla"}
|
||||
, {"source":"${HOME}/.cache/mozilla"}
|
||||
, {"source":"${HOME}/Downloads/"}
|
||||
|
||||
, {"source":"${HOME}/.config/gtk-3.0"}
|
||||
, {"source":"${HOME}/.config/gtk-2.0"}
|
||||
]
|
||||
, "blacklist": [
|
||||
]
|
||||
, "environment": [
|
||||
]
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
{
|
||||
"path": "/usr/bin/liferea"
|
||||
, "_wrapper": "/usr/bin/torify"
|
||||
, "xserver": {
|
||||
"enabled": true
|
||||
, "enable_tray": true
|
||||
, "tray_icon":"/usr/share/icons/hicolor/scalable/apps/liferea.svg"
|
||||
, "_tray_icon":"/usr/share/icons/gnome-colors-common/scalable/apps/liferea.svg"
|
||||
}
|
||||
, "network":{
|
||||
"nettype":"bridge"
|
||||
, "sockets": [
|
||||
{"nettype":"client", "proto":"tcp", "port":9050}
|
||||
]
|
||||
}
|
||||
, "whitelist": [
|
||||
{"source":"/run/resolvconf"}
|
||||
|
||||
, {"source":"${HOME}/.local/share/liferea"}
|
||||
, {"source":"${HOME}/.cache/liferea"}
|
||||
, {"source":"${HOME}/.config/liferea"}
|
||||
|
||||
, {"source":"${HOME}/.config/gtk-3.0"}
|
||||
, {"source":"${HOME}/.config/gtk-2.0"}
|
||||
|
||||
, {"source":"${HOME}/.config/dconf"}
|
||||
, {"source":"${HOME}/.cache/dconf"}
|
||||
, {"source":"/run/user/${UID}/dconf"}
|
||||
]
|
||||
, "blacklist": [
|
||||
]
|
||||
, "_environment": [
|
||||
]
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
{
|
||||
"path": "/usr/bin/pidgin"
|
||||
, "xserver": {
|
||||
"enabled": true
|
||||
, "enable_tray": true
|
||||
, "tray_icon":"/usr/share/icons/gnome-colors-common/scalable/apps/pidgin-menu.svg"
|
||||
}
|
||||
, "network":{
|
||||
"nettype":"bridge"
|
||||
}
|
||||
, "whitelist": [
|
||||
{"source":"${HOME}/.purple"}
|
||||
]
|
||||
, "blacklist": [
|
||||
]
|
||||
, "environment": [
|
||||
]
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
{
|
||||
"path": "/usr/local/bin/pond"
|
||||
, "xserver": {
|
||||
"enabled": true
|
||||
, "enable_tray": true
|
||||
, "tray_icon":"/usr/share/icons/gnome-colors-common/scalable/apps/office-mail.svg"
|
||||
, "window_icon":"/usr/share/icons/gnome-colors-common/scalable/apps/office-mail.svg"
|
||||
, "disable_audio":true
|
||||
}
|
||||
, "network":{
|
||||
"nettype":"empty"
|
||||
, "sockets": [
|
||||
{"nettype":"client", "proto":"tcp", "port":9050}
|
||||
, {"nettype":"client", "proto":"tcp", "port":30003}
|
||||
]
|
||||
}
|
||||
, "whitelist": [
|
||||
{"source":"${HOME}/.pond"}
|
||||
, {"source":"/opt/usr/share/gopkgs/pond"}
|
||||
]
|
||||
, "blacklist": [
|
||||
]
|
||||
, "environment": [
|
||||
{"name":"GOPATH", "value":"/opt/usr/share/gopkgs/pond"}
|
||||
, {"name":"TOR_SKIP_LAUNCH"}
|
||||
, {"name":"TOR_SOCKS_HOST"}
|
||||
, {"name":"TOR_SOCKS_PORT"}
|
||||
]
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
{
|
||||
"path": "/usr/bin/torbrowser-launcher"
|
||||
, "watchdog": "start-tor-browser"
|
||||
, "xserver": {
|
||||
"enabled": true
|
||||
, "enable_tray": true
|
||||
, "disable_audio": true
|
||||
, "disable_clipboard": false
|
||||
, "tray_icon":"/usr/share/pixmaps/torbrowser80.xpm"
|
||||
}
|
||||
, "network":{
|
||||
"nettype":"empty"
|
||||
, "sockets": [
|
||||
{"nettype":"client", "proto":"tcp", "port":9050}
|
||||
]
|
||||
}
|
||||
, "whitelist": [
|
||||
{"source":"${HOME}/.local/share/torbrowser"}
|
||||
, {"source":"${HOME}/.cache/torbrowser"}
|
||||
, {"source":"${HOME}/.config/torbrowser"}
|
||||
, {"source":"${HOME}/Downloads/TorBrowser"}
|
||||
]
|
||||
, "blacklist": [
|
||||
]
|
||||
, "environment": [
|
||||
{"name":"TOR_SKIP_LAUNCH"}
|
||||
, {"name":"TOR_SOCKS_HOST"}
|
||||
, {"name":"TOR_SOCKS_PORT"}
|
||||
]
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
{
|
||||
"path": "/usr/bin/xchat"
|
||||
, "xserver": {
|
||||
"enabled": true
|
||||
, "enable_tray": true
|
||||
, "tray_icon": "/usr/share/icons/gnome-colors-common/scalable/apps/xchat.svg"
|
||||
, "disable_audio": true
|
||||
}
|
||||
, "network":{
|
||||
"nettype":"empty"
|
||||
, "sockets": [
|
||||
{"nettype":"client", "destination": "10.10.0.90", "proto":"tcp", "port":57000}
|
||||
]
|
||||
}
|
||||
, "whitelist": [
|
||||
{"source":"${HOME}/.xpra/xchat"}
|
||||
, {"source":"${HOME}/.xchat2"}
|
||||
|
||||
, {"source":"${HOME}/.config/gtk-3.0"}
|
||||
, {"source":"${HOME}/.config/gtk-2.0"}
|
||||
]
|
||||
, "blacklist": [
|
||||
]
|
||||
}
|
Loading…
Reference in new issue