blob: 85936ec41ec1e11517c3b012d98579fc5095a44f [file] [log] [blame]
Jiri Simsa5293dcb2014-05-10 09:56:38 -07001package mounttable
2
3import (
Ryan Brownac972652014-05-19 10:57:32 -07004 "encoding/json"
Jiri Simsa5293dcb2014-05-10 09:56:38 -07005 "fmt"
Ryan Brownac972652014-05-19 10:57:32 -07006 "os"
Jiri Simsa5293dcb2014-05-10 09:56:38 -07007 "path"
8 "strings"
9 "sync"
10 "time"
11
Jiri Simsa519c5072014-09-17 21:37:57 -070012 "veyron.io/veyron/veyron/lib/glob"
13 vsecurity "veyron.io/veyron/veyron/security"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070014
Jiri Simsa519c5072014-09-17 21:37:57 -070015 "veyron.io/veyron/veyron2/ipc"
16 "veyron.io/veyron/veyron2/naming"
17 "veyron.io/veyron/veyron2/rt"
18 "veyron.io/veyron/veyron2/security"
19 "veyron.io/veyron/veyron2/services/mounttable"
20 "veyron.io/veyron/veyron2/services/mounttable/types"
21 "veyron.io/veyron/veyron2/verror"
22 "veyron.io/veyron/veyron2/vlog"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070023)
24
25var (
Asim Shankar45054a62014-05-15 10:32:54 -070026 errNamingLoop = verror.Make(verror.BadArg, "Loop in namespace")
Jiri Simsa5293dcb2014-05-10 09:56:38 -070027)
28
29// mountTable represents a namespace. One exists per server instance.
30type mountTable struct {
31 sync.RWMutex
32 root *node
Ryan Brownac972652014-05-19 10:57:32 -070033 acls map[string]security.Authorizer
Jiri Simsa5293dcb2014-05-10 09:56:38 -070034}
35
David Why Use Two When One Will Do Presottocfea09a2014-05-12 10:45:40 -070036// mountContext represents a client bind. The name is the name that was bound to.
Jiri Simsa5293dcb2014-05-10 09:56:38 -070037type mountContext struct {
38 name string
39 elems []string // parsed elements of name
40 cleanedElems []string // parsed elements of cleaned name (with leading /
41 // and double / removed).
42 mt *mountTable
43}
44
45// mount represents a single mount point. It contains OAs of all servers mounted
46// here. The servers are considered equivalent, i.e., RPCs to a name below this
47// point can be sent to any of these servers.
48type mount struct {
49 servers *serverList
50}
51
Jiri Simsa5293dcb2014-05-10 09:56:38 -070052// node is a single point in the tree representing the mount table.
53type node struct {
54 parent *node
55 mount *mount
Jiri Simsa5293dcb2014-05-10 09:56:38 -070056 children map[string]*node
57}
58
59// NewMountTable creates a new server that uses the default authorization policy.
Ryan Brownac972652014-05-19 10:57:32 -070060func NewMountTable(aclfile string) (*mountTable, error) {
61 acls, err := parseACLs(aclfile)
62 if err != nil {
63 return nil, err
64 }
Jiri Simsa5293dcb2014-05-10 09:56:38 -070065 return &mountTable{
66 root: new(node),
Ryan Brownac972652014-05-19 10:57:32 -070067 acls: acls,
68 }, nil
69}
70
71func parseACLs(path string) (map[string]security.Authorizer, error) {
72 if path == "" {
73 return nil, nil
Jiri Simsa5293dcb2014-05-10 09:56:38 -070074 }
Ryan Brownac972652014-05-19 10:57:32 -070075 var acls map[string]security.ACL
76 f, err := os.Open(path)
77 if err != nil {
78 return nil, err
79 }
80 defer f.Close()
81 if err = json.NewDecoder(f).Decode(&acls); err != nil {
82 return nil, err
83 }
84 result := make(map[string]security.Authorizer)
Ankur6387ef22014-05-23 17:24:27 -070085 for name, acl := range acls {
Tilak Sharma3ed30242014-08-11 11:45:55 -070086 result[name] = vsecurity.NewACLAuthorizer(acl)
Ryan Brownac972652014-05-19 10:57:32 -070087 }
88 if result["/"] == nil {
89 return nil, fmt.Errorf("No acl for / in %s", path)
90 }
91 return result, nil
Jiri Simsa5293dcb2014-05-10 09:56:38 -070092}
93
94// LookupServer implements ipc.Dispatcher.Lookup.
Cosmos Nicolaou8bfacf22014-08-19 11:19:36 -070095func (mt *mountTable) Lookup(name, method string) (ipc.Invoker, security.Authorizer, error) {
Cosmos Nicolaou69335402014-05-20 14:41:58 -070096 vlog.VI(2).Infof("*********************Lookup %s", name)
Jiri Simsa5293dcb2014-05-10 09:56:38 -070097 mt.RLock()
98 defer mt.RUnlock()
99 ms := &mountContext{
100 name: name,
101 mt: mt,
102 }
103 if len(name) > 0 {
104 ms.elems = strings.Split(name, "/")
105 ms.cleanedElems = strings.Split(strings.TrimLeft(path.Clean(name), "/"), "/")
106 }
Ryan Brownac972652014-05-19 10:57:32 -0700107 return ipc.ReflectInvoker(mounttable.NewServerMountTable(ms)), ms, nil
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700108}
109
110// findNode returns the node for the name path represented by elems. If none exists and create is false, return nil.
111// Otherwise create the path and return a pointer to the terminal node.
112func (mt *mountTable) findNode(elems []string, create bool) *node {
113 cur := mt.root
114
115 // Iterate down the tree.
116 for _, e := range elems {
117 // if we hit another mount table, we're done
118 if cur.mount != nil {
119 return nil
120 }
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700121 // then walk the children
122 c, ok := cur.children[e]
123 if ok {
124 cur = c
125 continue
126 }
127 if !create {
128 return nil
129 }
130 next := new(node)
131 if cur.children == nil {
132 cur.children = make(map[string]*node)
133 }
134 cur.children[e] = next
135 next.parent = cur
136 cur = next
137 }
138 return cur
139}
140
141// isActive returns true if a mount has unexpired servers attached.
142func (m *mount) isActive() bool {
143 if m == nil {
144 return false
145 }
146 return m.servers.removeExpired() > 0
147}
148
149// walk returns the first mount point node on the elems path and the suffix of elems below that mount point.
David Why Use Two When One Will Do Presotto8b3447c2014-05-13 13:55:20 -0700150// If no mount point is found, it returns nil,nil.
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700151func (mt *mountTable) walk(n *node, elems []string) (*node, []string) {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700152 if n.mount.isActive() {
153 return n, elems
154 } else if n.mount != nil {
155 n.removeUseless()
156 }
157 if len(elems) > 0 {
158 if c, ok := n.children[elems[0]]; ok {
159 if nn, nelems := mt.walk(c, elems[1:]); nn != nil {
160 return nn, nelems
161 }
162 }
163 }
164 return nil, nil
165}
166
Ryan Brownac972652014-05-19 10:57:32 -0700167func (mt *mountTable) authorizeStep(name string, c security.Context) error {
168 if mt.acls == nil {
169 return nil
170 }
171 mt.Lock()
Ryan Brownd123fa32014-05-19 10:57:32 -0700172 acl := mt.acls[name]
Ryan Brownac972652014-05-19 10:57:32 -0700173 mt.Unlock()
174 vlog.VI(2).Infof("authorizeStep(%s) %s %s %s", name, c.RemoteID(), c.Label(), acl)
175 if acl != nil {
176 return acl.Authorize(c)
177 }
178 return nil
179}
180
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700181func slashSlashJoin(elems []string) string {
182 if len(elems) == 2 && len(elems[0]) == 0 && len(elems[1]) == 0 {
183 return "//"
184 }
185 if len(elems) > 0 && len(elems[0]) == 0 {
186 return "/" + strings.Join(elems, "/")
187 }
188 return strings.Join(elems, "/")
189}
190
Ryan Brownac972652014-05-19 10:57:32 -0700191// Authorize verifies that the client has access to the requested node.
192// Checks the acls on all nodes in the path starting at the root.
193func (ms *mountContext) Authorize(context security.Context) error {
Ryan Brownd123fa32014-05-19 10:57:32 -0700194 if err := ms.mt.authorizeStep("/", context); err != nil {
Ryan Brownac972652014-05-19 10:57:32 -0700195 return err
196 }
Ryan Brownd123fa32014-05-19 10:57:32 -0700197 key := ""
Ankur6387ef22014-05-23 17:24:27 -0700198 for _, step := range ms.cleanedElems {
Ryan Brownd123fa32014-05-19 10:57:32 -0700199 key := key + "/" + step
Ryan Brownac972652014-05-19 10:57:32 -0700200 if err := ms.mt.authorizeStep(key, context); err != nil {
201 return err
202 }
203 }
204 return nil
205}
206
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700207// ResolveStep returns the next server in a resolution, the name remaining below that server,
208// and whether or not that server is another mount table.
Robin Thellend1da424d2014-08-21 12:41:09 -0700209func (ms *mountContext) ResolveStep(context ipc.ServerContext) (servers []types.MountedServer, suffix string, err error) {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700210 vlog.VI(2).Infof("ResolveStep %q", ms.name)
211 mt := ms.mt
212 // TODO(caprita): we need to grab a write lock because walk may
213 // garbage-collect expired servers. Rework this to avoid this potential
214 // bottleneck.
215 mt.Lock()
216 defer mt.Unlock()
217 // Find the next mount point for the name.
218 n, elems := mt.walk(mt.root, ms.elems)
219 if n == nil {
220 if len(ms.elems) == 0 {
221 return nil, ms.name, naming.ErrNoSuchNameRoot
222 }
223 return nil, ms.name, naming.ErrNoSuchName
224 }
225 return n.mount.servers.copyToSlice(), slashSlashJoin(elems), nil
226}
227
228// Mount a server onto the name in the receiver.
Matt Rosencrantzf5afcaf2014-06-02 11:31:22 -0700229func (ms *mountContext) Mount(context ipc.ServerContext, server string, ttlsecs uint32) error {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700230 mt := ms.mt
231 if ttlsecs == 0 {
232 ttlsecs = 10 * 365 * 24 * 60 * 60 // a really long time
233 }
Cosmos Nicolaou69335402014-05-20 14:41:58 -0700234 vlog.VI(2).Infof("*********************Mount %q -> %s", ms.name, server)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700235
236 // Make sure the server name is reasonable.
237 epString, _ := naming.SplitAddressName(server)
238 if _, err := rt.R().NewEndpoint(epString); err != nil {
239 return fmt.Errorf("malformed address %q for mounted server %q", epString, server)
240 }
241
242 // Find/create node in namespace and add the mount.
243 mt.Lock()
244 defer mt.Unlock()
245 n := mt.findNode(ms.cleanedElems, true)
246 if n == nil {
247 return naming.ErrNoSuchName
248 }
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700249 if n.mount == nil {
250 n.mount = &mount{
251 servers: NewServerList(),
252 }
253 }
254 m := n.mount
255 m.servers.add(server, time.Duration(ttlsecs)*time.Second)
256 return nil
257}
258
David Why Use Two When One Will Do Presotto8b3447c2014-05-13 13:55:20 -0700259// A useful node has children or an active mount.
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700260func (n *node) isUseful() bool {
Asim Shankar45054a62014-05-15 10:32:54 -0700261 return len(n.children) > 0 || n.mount.isActive()
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700262}
263
264// removeUseless removes a node and all of its ascendants that are not useful.
265func (n *node) removeUseless() {
266 if n.isUseful() {
267 return
268 }
269 if n.parent == nil {
270 return
271 }
272 for k, c := range n.parent.children {
273 if c == n {
274 delete(n.parent.children, k)
275 break
276 }
277 }
278 n.parent.removeUseless()
279}
280
281// removeUselessSubtree removes all descendant nodes of this node that are not
282// useful (after calling removeUselessSubtree recursively). Returns if this
283// node is useful anymore.
284func (n *node) removeUselessSubtree() bool {
285 for k, c := range n.children {
286 if !c.removeUselessSubtree() {
287 delete(n.children, k)
288 }
289 }
290 return n.isUseful()
291}
292
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700293// Unmount removes servers from the name in the receiver. If server is specified, only that
294// server is removed.
Matt Rosencrantzf5afcaf2014-06-02 11:31:22 -0700295func (ms *mountContext) Unmount(context ipc.ServerContext, server string) error {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700296 mt := ms.mt
297 mt.Lock()
298 defer mt.Unlock()
299 n := mt.findNode(ms.cleanedElems, false)
300 if n == nil {
301 return nil
302 }
303 defer n.removeUseless()
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700304 if server == "" {
305 n.mount = nil
306 return nil
307 }
308 if n.mount != nil && n.mount.servers.remove(server) == 0 {
309 n.mount = nil
310 }
311 return nil
312}
313
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700314// A struct holding a partial result of Glob.
315type globEntry struct {
316 n *node
317 name string
318}
319
Adam Sadovsky41cb9142014-07-31 10:42:54 -0700320func (mt *mountTable) globStep(n *node, name string, pattern *glob.Glob, context ipc.ServerContext, reply mounttable.GlobbableServiceGlobStream) {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700321 vlog.VI(2).Infof("globStep(%s, %s)", name, pattern)
322
Ryan Brownac972652014-05-19 10:57:32 -0700323 if mt.acls != nil {
Ryan Brownd123fa32014-05-19 10:57:32 -0700324 acl_name := "/" + strings.TrimLeft(naming.Join(context.Suffix(), name), "/")
Ryan Brownac972652014-05-19 10:57:32 -0700325 // Skip this node if the user isn't authorized.
Ryan Brownd123fa32014-05-19 10:57:32 -0700326 if acl := mt.acls[acl_name]; acl != nil {
Ryan Brownac972652014-05-19 10:57:32 -0700327 if err := acl.Authorize(context); err != nil {
328 return
329 }
330 }
331 }
332
Shyam Jayaraman97b9dca2014-07-31 13:30:46 -0700333 sender := reply.SendStream()
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700334 // If this is a mount point, we're done.
335 if m := n.mount; m != nil {
336 // Garbage-collect if expired.
337 if !m.isActive() {
338 n.removeUseless()
339 return
340 }
Robin Thellend1da424d2014-08-21 12:41:09 -0700341 sender.Send(types.MountEntry{Name: name, Servers: m.servers.copyToSlice()})
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700342 return
343 }
344
David Why Use Two When One Will Do Presotto8b3447c2014-05-13 13:55:20 -0700345 if pattern.Len() == 0 {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700346 // Garbage-collect if no useful descendants.
347 if !n.removeUselessSubtree() {
348 n.removeUseless()
349 return
350 }
Robin Thellend1da424d2014-08-21 12:41:09 -0700351 sender.Send(types.MountEntry{Name: name})
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700352 }
353
354 if pattern.Finished() {
355 return
356 }
357
358 // Recurse through the children.
359 for k, c := range n.children {
Tilak Sharma577ce8d2014-09-22 10:25:00 -0700360 if ok, _, suffix := pattern.MatchInitialSegment(k); ok {
Ken Ashcraft7ca37d92014-08-12 17:46:43 -0700361 mt.globStep(c, naming.Join(name, k), suffix, context, reply)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700362 }
363 }
364}
365
366// Glob finds matches in the namespace. If we reach a mount point before matching the
367// whole pattern, return that mount point.
368// pattern is a glob pattern as defined by the veyron/lib/glob package.
Adam Sadovsky41cb9142014-07-31 10:42:54 -0700369func (ms *mountContext) Glob(context ipc.ServerContext, pattern string, reply mounttable.GlobbableServiceGlobStream) error {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700370 vlog.VI(2).Infof("mt.Glob %v", ms.elems)
371
372 g, err := glob.Parse(pattern)
373 if err != nil {
374 return err
375 }
376
377 mt := ms.mt
378
379 // TODO(caprita): we need to grab a write lock because globStep may
380 // garbage-collect expired servers. Rework this to avoid this potential
381 // bottleneck.
382 mt.Lock()
383 defer mt.Unlock()
384
Robin Thellend434e39f2014-08-27 14:46:27 -0700385 // If the current name is not fully resolvable on this nameserver we
386 // don't need to evaluate the glob expression. Send a partially resolved
387 // name back to the client.
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700388 n := mt.findNode(ms.cleanedElems, false)
389 if n == nil {
Robin Thellend434e39f2014-08-27 14:46:27 -0700390 ms.linkToLeaf(reply)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700391 return nil
392 }
393
Ryan Brownac972652014-05-19 10:57:32 -0700394 mt.globStep(n, "", g, context, reply)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700395 return nil
396}
Robin Thellend434e39f2014-08-27 14:46:27 -0700397
398func (ms *mountContext) linkToLeaf(reply mounttable.GlobbableServiceGlobStream) {
399 n, elems := ms.mt.walk(ms.mt.root, ms.cleanedElems)
400 if n == nil {
401 return
402 }
403 servers := n.mount.servers.copyToSlice()
404 for i, s := range servers {
405 servers[i].Server = naming.Join(s.Server, slashSlashJoin(elems))
406 }
407 reply.SendStream().Send(types.MountEntry{Name: "", Servers: servers})
408}