blob: 2ea6e82fbaa696ff72ea6aaaf849761ac3c2b8b3 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"io/ioutil"
"os"
"path"
"time"
"v.io/v23"
"v.io/v23/context"
"v.io/v23/naming"
"v.io/v23/options"
"v.io/v23/security"
"v.io/x/lib/cmdline"
"v.io/x/lock"
"v.io/x/lock/locklib"
"v.io/x/ref/lib/v23cmd"
_ "v.io/x/ref/runtime/factories/static"
)
var (
lockGlobPattern = path.Join("nh", locklib.LockNhPrefix+"*")
cmdScan = &cmdline.Command{
Runner: v23cmd.RunnerFunc(runScan),
Name: "scan",
Short: "Scan the neighborhood for lock objects",
Long: `
Globs over the neighborhood to find names of lock objects (both claimed
and unclaimed).
`,
}
cmdClaim = &cmdline.Command{
Runner: v23cmd.RunnerFunc(runClaim),
Name: "claim",
Short: "Claim the specified lock with the provided name",
Long: `
Claims the specified unclaimed lock with the provided name, and authorizes the
principal executing this command to access the claimed lock object.
`,
ArgsName: "<lock> <name>",
ArgsLong: `
<lock> is the object name of the unclaimed lock.
<name> is a name that you'd like to give to the lock, for example,
"my_front_door" or "123_main_street.
`,
}
cmdLock = &cmdline.Command{
Runner: v23cmd.RunnerFunc(runLock),
Name: "lock",
Short: "Lock the specified lock",
Long: `
Locks the specified lock.
`,
ArgsName: "<lock>",
ArgsLong: `
<lock> is the object name of the lock.
`,
}
cmdUnlock = &cmdline.Command{
Runner: v23cmd.RunnerFunc(runUnlock),
Name: "unlock",
Short: "Unlock the specified lock",
Long: `
Unlocks the specified lock.
`,
ArgsName: "<lock>",
ArgsLong: `
<lock> is the object name of the lock.
`,
}
cmdStatus = &cmdline.Command{
Runner: v23cmd.RunnerFunc(runStatus),
Name: "status",
Short: "Print the current status of the specified lock",
Long: `
Prints the current status of the specified lock.
`,
ArgsName: "<lock>",
ArgsLong: `
<lock> is the object name of the lock.
`,
}
)
func runScan(ctx *context.T, env *cmdline.Env, args []string) error {
ctx, stop, err := withLocalNamespace(ctx)
if err != nil {
return err
}
defer stop()
locksFound := make(map[string]bool)
fmt.Println("Scanning for Locks...")
for {
ch, err := v23.GetNamespace(ctx).Glob(ctx, lockGlobPattern)
if err != nil {
return err
}
for v := range ch {
switch entry := v.(type) {
case *naming.GlobReplyEntry:
if name, servers := entry.Value.Name, entry.Value.Servers; len(name) != 0 && !locksFound[name] && len(servers) != 0 {
epStr, _ := naming.SplitAddressName(servers[0].Server)
ep, err := v23.NewEndpoint(epStr)
if err != nil {
continue
}
locksFound[name] = true
if bn := ep.BlessingNames(); len(bn) != 0 {
fmt.Printf("%v [owned by %v]\n", name, bn)
} else {
fmt.Printf("%v\n", name)
}
}
}
}
}
}
func runClaim(ctx *context.T, env *cmdline.Env, args []string) error {
if numargs := len(args); numargs != 2 {
return fmt.Errorf("requires exactly two arguments <lock>, <name>, provided %d", numargs)
}
lockname, name := args[0], args[1]
lockname = path.Join(lockname, locklib.LockSuffix)
ctx, stop, err := withLocalNamespace(ctx)
if err != nil {
return err
}
defer stop()
ctx, cancel := context.WithTimeout(ctx, time.Minute)
defer cancel()
// TODO(ataly): We should not skip server endpoint authorization while
// claiming locks but instead fetch the blessing root of the lock manufacturer
// from an authoritative source and then appropriately authenticate the server.
b, err := lock.UnclaimedLockClient(lockname).Claim(ctx, name, options.SkipServerEndpointAuthorization{})
if err != nil {
return err
}
p := v23.GetPrincipal(ctx)
if err := p.AddToRoots(b); err != nil {
return fmt.Errorf("failed to add (key) blessing (%v) to roots: %v", b, err)
}
if _, err := p.BlessingStore().Set(b, security.BlessingPattern(name)); err != nil {
return fmt.Errorf("failed to set (key) blessing (%v) for peer %v: %v", b, name, err)
}
fmt.Printf("Claimed lock and received key: %v\n", b)
return nil
}
func runLock(ctx *context.T, env *cmdline.Env, args []string) error {
return updateStatus(ctx, args, lock.Locked)
}
func runUnlock(ctx *context.T, env *cmdline.Env, args []string) error {
return updateStatus(ctx, args, lock.Unlocked)
}
func updateStatus(ctx *context.T, args []string, status lock.LockStatus) error {
if numargs := len(args); numargs != 1 {
return fmt.Errorf("requires exactly one arguments <lock>, provided %d", numargs)
}
lockname := args[0]
lockname = path.Join(lockname, locklib.LockSuffix)
ctx, stop, err := withLocalNamespace(ctx)
if err != nil {
return err
}
defer stop()
ctx, cancel := context.WithTimeout(ctx, time.Minute)
defer cancel()
if status == lock.Locked {
err = lock.LockClient(lockname).Lock(ctx)
} else {
err = lock.LockClient(lockname).Unlock(ctx)
}
if err != nil {
return err
}
fmt.Printf("Updated lock %v to status: %v\n", lockname, status)
return nil
}
func runStatus(ctx *context.T, env *cmdline.Env, args []string) error {
if numargs := len(args); numargs != 1 {
return fmt.Errorf("requires exactly one arguments <lock>, provided %d", numargs)
}
lockname := args[0]
lockname = path.Join(lockname, locklib.LockSuffix)
ctx, stop, err := withLocalNamespace(ctx)
if err != nil {
return err
}
defer stop()
ctx, cancel := context.WithTimeout(ctx, time.Minute)
defer cancel()
status, err := lock.LockClient(lockname).Status(ctx)
if err != nil {
return err
}
fmt.Printf("lock %v is: %v\n", lockname, status)
return nil
}
// Starts a mounttable server for listening to lock server location
// advertisements, and returns a new context derived from the provided
// one by attaching a namespace instance rooted at the started
// mounttable server.
func withLocalNamespace(ctx *context.T) (*context.T, func(), error) {
configDir, err := ioutil.TempDir("", "mounttable-config")
if err != nil {
return nil, nil, err
}
// TODO(ataly): Currently a non-empty neighborhood name must be provided
// to StartMounttable in order to make it listen to MDNS advertisements.
// This has the downside that it also makes the started mounttable
// adverstised itself under the provided name. Below the string "ignore"
// is used as the neighborhood name in order to easily identify (and ignore)
// the namespace under it in other mounttables. The right solution is to
// start the mounttable so that it only listens to MDNS advertisements but
// does not advertise itself.
mtName, stopMT, err := locklib.StartMounttable(ctx, configDir, "ignore")
if err != nil {
os.RemoveAll(configDir)
return nil, nil, err
}
stop := func() {
stopMT()
os.RemoveAll(configDir)
}
ctx, _, err = v23.WithNewNamespace(ctx, mtName)
if err != nil {
stop()
return nil, nil, err
}
return ctx, stop, nil
}
func main() {
cmdline.HideGlobalFlagsExcept()
root := &cmdline.Command{
Name: "lock",
Short: "claim and manage locks",
Long: `
Command lock claims and manages lock objects.
`,
Children: []*cmdline.Command{cmdScan, cmdClaim, cmdLock, cmdUnlock, cmdStatus},
}
cmdline.Main(root)
}