/
mandoc.go
172 lines (149 loc) · 3.57 KB
/
mandoc.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package convert
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"os"
"os/exec"
"strings"
"syscall"
"golang.org/x/sync/errgroup"
)
// Process starts a mandoc process to convert manpages to HTML.
type Process struct {
mandocConn *net.UnixConn
mandocProcess *os.Process
stopWait chan bool
}
func NewProcess() (*Process, error) {
p := &Process{}
return p, p.initMandoc()
}
func (p *Process) Kill() error {
if p.mandocProcess == nil {
return nil
}
p.stopWait <- true
return p.mandocProcess.Kill()
}
func (p *Process) initMandoc() error {
pair, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
if err != nil {
return err
}
// Use pair[0] in the parent process
syscall.CloseOnExec(pair[0])
f := os.NewFile(uintptr(pair[0]), "")
fc, err := net.FileConn(f)
if err != nil {
return err
}
conn := fc.(*net.UnixConn)
path, err := exec.LookPath("mandocd")
if err != nil {
if ee, ok := err.(*exec.Error); ok && ee.Err == exec.ErrNotFound {
log.Printf("mandocd not found, falling back to fork+exec for each manpage")
return nil
}
return err
}
cmd := exec.Command(path, "-Thtml", "3") // Go dup2()s ExtraFiles to 3 and onwards
cmd.ExtraFiles = []*os.File{os.NewFile(uintptr(pair[1]), "")}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
return err
}
p.stopWait = make(chan bool)
go func() {
wait := make(chan error, 1)
go func() {
wait <- cmd.Wait()
}()
select {
case <-p.stopWait:
return
case err := <-wait:
log.Fatalf("mandoc unexpectedly exited: %v", err)
}
}()
p.mandocProcess = cmd.Process
p.mandocConn = conn
return nil
}
func (p *Process) mandoc(r io.Reader) (stdout string, stderr string, err error) {
if p.mandocConn != nil {
stdout, stderr, err = p.mandocUnix(r)
} else {
stdout, stderr, err = p.mandocFork(r)
}
// TODO(later): once a new-enough version of mandoc is in Debian,
// get rid of this compatibility code by changing our CSS to not
// rely on the mandoc class at all anymore.
if err == nil && !strings.HasPrefix(stdout, `<div class="mandoc">`) {
stdout = `<div class="mandoc">
` + stdout + `</div>
`
}
return stdout, stderr, err
}
func (p *Process) mandocFork(r io.Reader) (stdout string, stderr string, err error) {
var stdoutb, stderrb bytes.Buffer
cmd := exec.Command("mandoc", "-Ofragment", "-Thtml")
cmd.Stdin = r
cmd.Stdout = &stdoutb
cmd.Stderr = &stderrb
if err := cmd.Run(); err != nil {
return "", "", fmt.Errorf("%v, stderr: %s", err, stderrb.String())
}
return stdoutb.String(), stderrb.String(), nil
}
func (p *Process) mandocUnix(r io.Reader) (stdout string, stderr string, err error) {
manr, manw, err := os.Pipe()
if err != nil {
return "", "", err
}
defer manr.Close()
defer manw.Close()
outr, outw, err := os.Pipe()
if err != nil {
return "", "", err
}
defer outr.Close()
defer outw.Close()
errr, errw, err := os.Pipe()
if err != nil {
return "", "", err
}
defer errr.Close()
defer errw.Close()
scm := syscall.UnixRights(int(manr.Fd()), int(outw.Fd()), int(errw.Fd()))
if _, _, err := p.mandocConn.WriteMsgUnix(nil, scm, nil); err != nil {
return "", "", err
}
manr.Close()
outw.Close()
errw.Close()
var eg errgroup.Group
eg.Go(func() error {
if _, err := io.Copy(manw, r); err != nil {
return err
}
return manw.Close()
})
var stdoutb, stderrb []byte
eg.Go(func() error {
var err error
stdoutb, err = ioutil.ReadAll(outr)
return err
})
eg.Go(func() error {
var err error
stderrb, err = ioutil.ReadAll(errr)
return err
})
return string(stdoutb), string(stderrb), eg.Wait()
}