Skip to content

Commit

Permalink
Handle plumbing messages on Plan 9.
Browse files Browse the repository at this point in the history
This does a first pass of allowing deplumber to handle plumbing messages
on Plan 9, where the unix domain socket hack for coordinating the open
window doesn't work because there are no unix domain sockets.

This needs some work, and should create a proper 9fs server under /mnt/de,
rather than trying to port the hack, but for now it works, though it always
opens things in a new window.
  • Loading branch information
driusan committed Dec 31, 2017
1 parent e61dded commit 438ceb1
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 15 deletions.
2 changes: 1 addition & 1 deletion actions/plumb.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func plumb(content []byte, buff *demodel.CharBuffer, v demodel.Viewport, click i
}
fid, err := plumblib.Open("send", plan9.OWRITE)
if err != nil {
fmt.Printf("%v", err)
fmt.Printf("Plumbing error: %v", err)
return err
}

Expand Down
103 changes: 90 additions & 13 deletions deplumber/main_plan9.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,40 @@ package main

import (
"fmt"
"io"
"io/ioutil"
"log"
"net"
"os"
"os/exec"
"os/signal"
"regexp"
"strconv"
"strings"
"time"

"9fans.net/go/plan9"
"9fans.net/go/plumb"
)

const ORCLOSE = 64

// We keep track of the most recent de process that we got a message from,
// and make the assumption that that's the window that should be used for
// plumbing messages. Since plumbing messages are generated from the user
// doing something, we can assume that the user did something in that window
// in order to generate the message.
var activeProcess struct {
Conn net.Conn
Conn *os.File
PID uint64
Dirty bool
}

// Deletes the ~/.de/deplumber file just before dying. This should be called
// before log.Fatal() or from any signal handler.
func cleanupBeforeDeath() {
os.Remove("/tmp/deplumber")
if activeProcess.Conn != nil {
activeProcess.Conn.Close()
}
}
func plumbListener() {
f, err := plumb.Open("edit", plan9.OREAD)
Expand All @@ -50,14 +58,19 @@ func plumbListener() {
return
}

var filename, fakefile string
//fmt.Printf("%v", m)
var filename, fakefile, linespec string
for attr := m.Attr; attr != nil; attr = attr.Next {
// If there's a filename attribute, it's a synthetic
// file and Data is the content, not the filename.
if attr.Name == "filename" {
switch attr.Name {
case "filename":
filename = attr.Value
case "addr":
linespec = attr.Value
default:
//fmt.Printf("%v\n", attr)
}
fmt.Printf("Filename: %v", attr)
}
// If we've received a plumbing message from de, we determine
// whether or not to receive a new de window by checking if the
Expand All @@ -66,14 +79,35 @@ func plumbListener() {
//
// If the message didn't come from de, we always spawn a new
// window.
if !activeProcess.Dirty && activeProcess.PID != 0 && m.Src == "de" {
fmt.Fprintf(activeProcess.Conn, "%s\n", m.Data)
if false && !activeProcess.Dirty && activeProcess.PID != 0 && m.Src == "de" {
// This needs to be reworked, so for now just open everything in a new
// window..
fmt.Fprintf(activeProcess.Conn, "\n%d:%s\n", activeProcess.PID, m.Data)
if err := ioutil.WriteFile(
fmt.Sprintf("/proc/%d/note", activeProcess.PID),
[]byte(fmt.Sprintf("open file: %v", m.Data)),
0700,
); err != nil {
log.Println(err)
}
} else {
var cmd *exec.Cmd
var args []string
if m.Dir != "" {
args = append(args, "-cd", m.Dir)
}
if filename == "" {
// There was no filename attribute, so data
// is the filename.
cmd = exec.Command("window", "de", string(m.Data))
//
// First, make it relative to dir if possible.
fname := strings.TrimPrefix(string(m.Data), m.Dir+"/")
if linespec != "" {
args = append(args, "de", fname+":"+linespec)
} else {
args = append(args, "de", fname)
}
cmd = exec.Command("window", args...)
} else {
// There was a filename attribute, so data
// is the content of that "file"
Expand All @@ -95,8 +129,14 @@ func plumbListener() {
// and pretend that the filename is whatever was
// in the plumb message.
fakefile = f.Name()
cmd = exec.Command("window", "de", "-delete", "-filename", `'`+filename+`'`, fakefile)
var args []string
if m.Dir != "" {
args = append(args, "-cd", m.Dir)
}
args = append(args, "de", "-delete", "-filename", `'`+filename+`'`, fakefile)
cmd = exec.Command("window", args...)
}
cmd.Dir = m.Dir
if err := cmd.Start(); err != nil {
if fakefile != "" {
os.Remove(fakefile)
Expand All @@ -108,10 +148,15 @@ func plumbListener() {
}

func main() {
if data, err := ioutil.ReadFile("/tmp/deplumber"); err == nil && string(data) != "" {
// If there was nothing else running, it should be a not found error.
if _, err := os.Stat("/tmp/deplumber"); err == nil {
log.Fatalf("Another deplumber instance appears to already be running. If this is not correct, please delete the file /tmp/deplumber\n")
}
ioutil.WriteFile("/tmp/deplumber", []byte("I'm alive!"), 0600)
data, err := os.OpenFile("/tmp/deplumber", os.O_RDONLY|ORCLOSE|os.O_CREATE, 0600)
if err != nil {
log.Fatal(err)
}
activeProcess.Conn = data

// Catch any note that will kill the process, so that we can
// clean up.
Expand All @@ -125,5 +170,37 @@ func main() {
log.Fatalf("Killed by signal %s", s)
}()

plumbListener()
// Start a goroutine to listen for plumbing events
go plumbListener()
msg := make([]byte, 1024)
msgRE, err := regexp.Compile(`([\d]+):(.+)`)
if err != nil {
panic(err)
}
for {
n, err := data.Read(msg)
if err == io.EOF || n == 0 {
time.Sleep(1 * time.Second)
} else if err != nil {
panic(err)
} else {
if match := msgRE.FindStringSubmatch(string(msg)); len(match) == 3 {
if pid, err := strconv.Atoi(match[1]); err == nil {
activeProcess.PID = uint64(pid)
}
switch match[2] {
case "Clean":
activeProcess.Dirty = false
case "Dirty":
activeProcess.Dirty = true
default:
fmt.Fprintf(os.Stderr, "Unknown state for PID %v\n", match[1])
}
//fmt.Printf("%v", match)
} else {
fmt.Printf("%v %v", n, string(msg))
}

}
}
}
4 changes: 3 additions & 1 deletion plumber.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// +build !plan9

package main

import (
Expand Down Expand Up @@ -83,7 +85,7 @@ func (p *plumbService) Connect(dirtyChan chan bool) {
// Returns whether the plumbing service is available and ready
// to plumb messages
func (p *plumbService) Available() bool {
return p.ready && p.conn != nil
return p.ready
}

// Goes into an infinite loop monitoring the dirtyChan
Expand Down
135 changes: 135 additions & 0 deletions plumber_plan9.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package main

import (
//"bufio"
"fmt"
"os"
// "strings"
)

// a plumbService coordinates communication between a de process, and a deplumber
// process.
//
// (The deplumber process communicates with the p9p plumber and receives messages.
// It decides whether or not to spawn a new window or re-use the existing one as
// best as it can.)
type plumbService struct {

// A channel which communicates that a file named
// string should be opened.
OpenChan chan string

// A channel on which the main thread communicates the
// clean/dirty status of its buffer to us.
DirtyChan chan bool

// A channel which the plumbService communicates errors over.
ErrorChan chan error

// The file used for communication with the deplumber service
conn *os.File

// Set to true at the end of initialization, so that Connect()
// doesn't need to block and Available() will work.
ready bool
}

// Connect connects the plumbService to a deplumber instance, and returns
// channels that it may asynchronously communicate with the main thread
// over.
//
// In particular, if there's any errors they will be sent over the errors
// channel instead of returned directly, to avoid having to block when calling
// connect.
//
// dirtyChan is a channel that de communicates the buffer dirty status to
// the plumbService over.
func (p *plumbService) Connect(dirtyChan chan bool) {
p.ErrorChan = make(chan error, 1)
p.OpenChan = make(chan string)
p.DirtyChan = dirtyChan
// Read the ~/.de/deplumber file to see where we should connect
file, err := os.OpenFile("/tmp/deplumber", os.O_RDWR|os.O_APPEND, 0600)

if err != nil {
p.ErrorChan <- fmt.Errorf("deplumber not started. Plumbing not available.")
close(p.ErrorChan)
return
}

p.conn = file

// Monitor the dirtyChan for messages from the main thread saying
// our dirty bit has changed, and inform the deplumber as appropriate.
go p.dirtyMonitor()

// We're now ready to receive messages and monitor the connection for
// new files that we should open.
go p.monitorOpenChan()
return
}

// Returns whether the plumbing service is available and ready
// to plumb messages
func (p *plumbService) Available() bool {
return p.ready
}

// Goes into an infinite loop monitoring the dirtyChan
// for changes in buffer status, and forwards them to the
// deplumber connection.
//
// This should only be called from a goroutine after the
// connection is initialized. It communicates from de to
// the deplumber.
func (p *plumbService) dirtyMonitor() {
for {
dirty := <-p.DirtyChan

if dirty {
fmt.Fprintf(p.conn, "%d:Dirty\n", os.Getpid())
} else {
fmt.Fprintf(p.conn, "%d:Clean\n", os.Getpid())
}
}
}

// Goes into an infinite loop, reading messages from the socket connection
// and sending them across the OpenChan
//
// This should also only be called from a goroutine after the connection is
// initialized. It communicates from the deplumber to de.
func (p *plumbService) monitorOpenChan() {
// We've connected to the deplumber service, so send it our PID and tell
// it we have a clean buffer
fmt.Fprintf(p.conn, "%d:Clean\n", os.Getpid())

p.ready = true
c := make(chan os.Signal)

for {
select {
case s := <-c:
fmt.Printf("%v", s)
p.ErrorChan <- fmt.Errorf("%v", s)
}
}
//r := bufio.NewReader(p.conn)

/*
for {
file, err := r.ReadString('\n')
switch err {
case io.EOF:
}
if err != nil {
println("foo")
p.ErrorChan <- err
p.ready = false
return
}
file = strings.TrimSpace(file)
p.OpenChan <- file
}
*/
}
3 changes: 3 additions & 0 deletions vendor/9fans.net/go/plumb/plumb_open_plan9.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 438ceb1

Please sign in to comment.