-
Notifications
You must be signed in to change notification settings - Fork 18
/
walk.go
207 lines (192 loc) · 5.36 KB
/
walk.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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
package styx
import (
"errors"
"fmt"
"os"
"path"
"strings"
"context"
"aqwari.net/net/styx/internal/styxfile"
"aqwari.net/net/styx/styxproto"
)
// The walk RPC is one of the more complex requests in the 9P protocol
// (though that's not saying much). There are a number of different
// responses that a server must make:
//
// - If the file exists: Rwalk with nwname qids
// - If no elements in the path exist: Rerror
// - If at least 1 element in the path exists: Rwalk with n(<nwname) qids
//
// In addition, walks are relative to another file, so a server must track
// that as well. The styx package attempts to hide this complexity from the
// (API) user.
//
// For any Twalk requests with nwname > 1, the styx package will synthesize
// nwname individual Twalk requests, such that each request is the next
// element in the path.
//
// The relative path in the Twalk request is combined with the path of fid
// to produce an absolute path to the requested file. All paths are Cleaned
// before being passed to user code.
//
// Thus given the following protocol message (fids replaced with their paths):
//
// Twalk /usr/share 6 ../include/linux/../../bin
//
// The user's program will instead see and respond to, *IN THIS ORDER*
//
// Twalk /usr
// Twalk /usr/include
// Twalk /usr/include/linux
// Twalk /usr/include
// Twalk /usr
// Twalk /usr/bin
//
// The order that the program sees the path in is important, as it allows
// certain synthetic file systems to create resources "on-demand", as the
// client asks for them.
type walkElem struct {
index int
qid styxproto.Qid // nil if not present
err error
}
type walker struct {
qids, found []styxproto.Qid
filled []int32
count int
complete chan struct{}
collect chan walkElem
newfid uint32
path string
// for cancellation
ctx context.Context
session *Session
tag uint16
}
func newWalker(s *Session, ctx context.Context, msg styxproto.Twalk, base string, elem ...string) *walker {
qids := make([]styxproto.Qid, len(elem))
found := qids[:0]
newpath := path.Join(base, strings.Join(elem, "/"))
w := &walker{
qids: qids,
found: found,
filled: make([]int32, len(elem)),
complete: make(chan struct{}),
collect: make(chan walkElem),
session: s,
newfid: msg.Newfid(),
path: newpath,
tag: msg.Tag(),
ctx: ctx,
}
go w.run()
return w
}
// runs in its own goroutine
func (w *walker) run() {
var err error
Loop:
for {
select {
case <-w.ctx.Done():
break Loop
case el, ok := <-w.collect:
if !ok {
break Loop
}
if el.err != nil {
err = el.err
}
w.count++
w.qids[el.index] = el.qid
for i := len(w.found); i < cap(w.found); i++ {
if w.qids[i] != nil {
w.found = w.found[:i+1]
}
}
if w.count == len(w.qids) {
break Loop
}
}
}
close(w.complete)
if !w.session.conn.clearTag(w.tag) {
return
}
if len(w.found) == 0 {
if err != nil {
w.session.conn.Rerror(w.tag, "%s", err)
} else {
w.session.conn.Rerror(w.tag, "No such file or directory")
}
} else {
w.session.files.Put(w.newfid, file{name: w.path})
w.session.conn.sessionFid.Put(w.newfid, w.session)
w.session.IncRef()
if err := w.session.conn.Rwalk(w.tag, w.found...); err != nil {
panic(err) // should never happen
}
}
w.session.conn.Flush()
}
// A client sends a Twalk message both to probe if a file exists, and to
// move a "cursor" within the filesystem hierarchy. In a traditional file
// system, a Twalk request is similar to using chdir to change the current
// directory. File servers are free to attach additional meaning to Twalk
// requests. For instance, a server may create directories on-demand as
// clients walk to them.
//
// The 9P protocol allows for clients to walk multiple directories with
// a single 9P message. The styx package translates such requests into
// multiple Twalk values, providing the following guarantees:
//
// - Path() will return a cleaned, absolute path
// - Consecutive, related Twalk requests will differ by at
// most 1 path element.
//
// The default response to a Twalk request is an Rerror message saying
// "No such file or directory".
type Twalk struct {
index int
walk *walker
reqInfo
}
func (t Twalk) WithContext(ctx context.Context) Request {
t.ctx = ctx
return t
}
func (t Twalk) handled() bool {
return t.walk.filled[t.index] == 1
}
// Rwalk signals to the client that the file named by the Twalk's
// Path method exists and is of the given mode. The permission bits of
// mode are ignored, and only the file type bits, such as os.ModeDir,
// are sent to the client. If err is non-nil, an error response is sent to the
// client instead.
func (t Twalk) Rwalk(info os.FileInfo, err error) {
var qid styxproto.Qid
var mode os.FileMode
if err == nil {
mode = info.Mode()
fqid, found := t.session.conn.getQid(t.Path(), styxfile.QidType(styxfile.Mode9P(mode)))
if !found {
err = errors.New("rwalk did not find file")
} else {
qid = fqid
}
}
t.walk.filled[t.index] = 1
elem := walkElem{qid: qid, index: t.index, err: err}
select {
case t.walk.collect <- elem:
case <-t.walk.complete:
}
}
// Rerror signals to the client that the file named by the Twalk's
// Path method does not exist.
func (t Twalk) Rerror(format string, args ...interface{}) {
t.Rwalk(nil, fmt.Errorf(format, args...))
}
func (t Twalk) defaultResponse() {
t.Rerror("No such file or directory")
}