diff --git a/oz-daemon/launch.go b/oz-daemon/launch.go index 4c1235d..bef43da 100644 --- a/oz-daemon/launch.go +++ b/oz-daemon/launch.go @@ -11,6 +11,7 @@ import ( "os/exec" "os/user" "path" + "sync" "syscall" ) @@ -27,6 +28,7 @@ type Sandbox struct { stderr io.ReadCloser addr string xpra *xpra.Xpra + ready sync.WaitGroup } /* @@ -95,7 +97,14 @@ func (d *daemonState) launch(p *oz.Profile, uid, gid uint32) (*Sandbox, error) { addr: path.Join(fs.Root(), "tmp", "oz-init-control"), stderr: pp, } + sbox.ready.Add(1) go sbox.logMessages() + if sbox.profile.XServer.Enabled { + go func() { + sbox.ready.Wait() + go sbox.startXpraClient() + }() + } d.nextSboxId += 1 d.sandboxes = append(d.sandboxes, sbox) return sbox, nil @@ -115,11 +124,13 @@ func (sbox *Sandbox) remove() { func (sbox *Sandbox) logMessages() { scanner := bufio.NewScanner(sbox.stderr) + seenOk := false for scanner.Scan() { line := scanner.Text() - if line == "XPRA READY" { - sbox.daemon.log.Info("Xpra server is ready for connection") - go sbox.startXpraClient() + if line == "OK" && !seenOk { + sbox.daemon.log.Info("oz-init (%s) is ready", sbox.profile.Name) + seenOk = true + sbox.ready.Done() } else if len(line) > 1 { sbox.logLine(line) } diff --git a/oz-init/init.go b/oz-init/init.go index 9d4c809..2385a8f 100644 --- a/oz-init/init.go +++ b/oz-init/init.go @@ -15,20 +15,22 @@ import ( "os/user" "strconv" "strings" + "sync" "syscall" ) const SocketAddress = "/tmp/oz-init-control" type initState struct { - log *logging.Logger - profile *oz.Profile - config *oz.Config - uid int - gid int - user *user.User - display int - fs *fs.Filesystem + log *logging.Logger + profile *oz.Profile + config *oz.Config + uid int + gid int + user *user.User + display int + fs *fs.Filesystem + xpraReady sync.WaitGroup } // By convention oz-init writes log messages to stderr with a single character @@ -121,15 +123,18 @@ func (st *initState) runInit() { st.log.Error("Error: setting up filesystem failed: %v\n", err) os.Exit(1) } + oz.ReapChildProcs(st.log, st.handleChildExit) + if st.profile.XServer.Enabled { if st.display == 0 { st.log.Error("Cannot start xpra because no display number was passed to oz-init") os.Exit(1) } + st.xpraReady.Add(1) st.startXpraServer() } - - oz.ReapChildProcs(st.log, st.handleChildExit) + st.xpraReady.Wait() + st.launchApplication() s, err := ipc.NewServer(SocketAddress, messageFactory, st.log, handlePing, @@ -142,6 +147,7 @@ func (st *initState) runInit() { if err := os.Chown(SocketAddress, st.uid, st.gid); err != nil { st.log.Warning("Failed to chown oz-init control socket: %v", err) } + os.Stderr.WriteString("OK\n") if err := s.Run(); err != nil { st.log.Warning("MsgServer.Run() return err: %v", err) @@ -178,11 +184,13 @@ func (st *initState) startXpraServer() { func (st *initState) readXpraOutput(r io.ReadCloser) { sc := bufio.NewScanner(r) + seenReady := false for sc.Scan() { line := sc.Text() if len(line) > 0 { - if strings.Contains(line, "xpra is ready.") { - os.Stderr.WriteString("XPRA READY\n") + if strings.Contains(line, "xpra is ready.") && !seenReady { + seenReady = true + st.xpraReady.Done() if !st.config.LogXpra { r.Close() return @@ -195,6 +203,42 @@ func (st *initState) readXpraOutput(r io.ReadCloser) { } } +func (st *initState) launchApplication() { + cmd := exec.Command(st.profile.Path) + stdout, err := cmd.StdoutPipe() + if err != nil { + st.log.Warning("Failed to create stdout pipe: %v", err) + return + } + stderr, err := cmd.StderrPipe() + if err != nil { + st.log.Warning("Failed to create stderr pipe: %v", err) + return + } + cmd.SysProcAttr = &syscall.SysProcAttr{} + cmd.SysProcAttr.Credential = &syscall.Credential{ + Uid: uint32(st.uid), + Gid: uint32(st.gid), + } + cmd.Env = []string{ + fmt.Sprintf("DISPLAY=:%d", st.display), + } + if err := cmd.Start(); err != nil { + st.log.Warning("Failed to start application (%s): %v", st.profile.Path, err) + return + } + go st.readApplicationOutput(stdout, "stdout") + go st.readApplicationOutput(stderr, "stderr") +} + +func (st *initState) readApplicationOutput(r io.ReadCloser, label string) { + sc := bufio.NewScanner(r) + for sc.Scan() { + line := sc.Text() + st.log.Debug("(%s) %s", label, line) + } +} + func loadProfile(dir, name string) (*oz.Profile, error) { ps, err := oz.LoadProfiles(dir) if err != nil {