vitess.io/vitess@v0.16.2/go/cmd/zk/zkcmd.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreedto in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package main
    18  
    19  import (
    20  	"archive/zip"
    21  	"bytes"
    22  	"context"
    23  	"fmt"
    24  	"io"
    25  	"os"
    26  	"os/exec"
    27  	"os/signal"
    28  	"path"
    29  	"sort"
    30  	"strings"
    31  	"sync"
    32  	"syscall"
    33  	"time"
    34  
    35  	"github.com/spf13/pflag"
    36  	"github.com/z-division/go-zookeeper/zk"
    37  	"golang.org/x/term"
    38  
    39  	"vitess.io/vitess/go/acl"
    40  	"vitess.io/vitess/go/exit"
    41  	"vitess.io/vitess/go/vt/log"
    42  	"vitess.io/vitess/go/vt/logutil"
    43  	"vitess.io/vitess/go/vt/topo"
    44  	"vitess.io/vitess/go/vt/topo/zk2topo"
    45  )
    46  
    47  var doc = `
    48  zk is a tool for wrangling the zookeeper
    49  
    50  It tries to mimic unix file system commands wherever possible, but
    51  there are some slight differences in flag handling.
    52  
    53  zk -h - provide help on overriding cell selection
    54  
    55  zk addAuth digest user:pass
    56  
    57  zk cat /zk/path
    58  zk cat -l /zk/path1 /zk/path2 (list filename before file data)
    59  
    60  zk chmod n-mode /zk/path
    61  zk chmod n+mode /zk/path
    62  
    63  zk cp /zk/path .
    64  zk cp ./config /zk/path/config
    65  zk cp ./config /zk/path/ (trailing slash indicates directory)
    66  
    67  zk edit /zk/path (create a local copy, edit and write changes back to cell)
    68  
    69  zk ls /zk
    70  zk ls -l /zk
    71  zk ls -ld /zk (list directory node itself)
    72  zk ls -R /zk (recursive, expensive)
    73  
    74  zk stat /zk/path
    75  
    76  zk touch /zk/path
    77  zk touch -c /zk/path (don't create, just touch timestamp)
    78  zk touch -p /zk/path (create all parts necessary, think mkdir -p)
    79  NOTE: there is no mkdir - just touch a node. The distinction
    80  between file and directory is just not relevant in zookeeper.
    81  
    82  zk rm /zk/path
    83  zk rm -r /zk/path (recursive)
    84  zk rm -f /zk/path (no error on nonexistent node)
    85  
    86  zk wait /zk/path (wait for node change or creation)
    87  zk wait /zk/path/children/ (trailing slash waits on children)
    88  
    89  zk watch /zk/path (print changes)
    90  
    91  zk unzip zktree.zip /
    92  zk unzip zktree.zip /zk/prefix
    93  
    94  zk zip /zk/root zktree.zip
    95  NOTE: zip file can't be dumped to the file system since znodes
    96  can have data and children.
    97  
    98  The zk tool looks for the address of the cluster in /etc/zookeeper/zk_client.conf,
    99  or the file specified in the ZK_CLIENT_CONFIG environment variable.
   100  
   101  The local cell may be overridden with the ZK_CLIENT_LOCAL_CELL environment
   102  variable.
   103  `
   104  
   105  const (
   106  	timeFmt      = "2006-01-02 15:04:05"
   107  	timeFmtMicro = "2006-01-02 15:04:05.000000"
   108  )
   109  
   110  type cmdFunc func(ctx context.Context, subFlags *pflag.FlagSet, args []string) error
   111  
   112  var cmdMap map[string]cmdFunc
   113  var zconn *zk2topo.ZkConn
   114  var server string
   115  
   116  func init() {
   117  	cmdMap = map[string]cmdFunc{
   118  		"addAuth": cmdAddAuth,
   119  		"cat":     cmdCat,
   120  		"chmod":   cmdChmod,
   121  		"cp":      cmdCp,
   122  		"edit":    cmdEdit,
   123  		"ls":      cmdLs,
   124  		"rm":      cmdRm,
   125  		"stat":    cmdStat,
   126  		"touch":   cmdTouch,
   127  		"unzip":   cmdUnzip,
   128  		"wait":    cmdWait,
   129  		"watch":   cmdWatch,
   130  		"zip":     cmdZip,
   131  	}
   132  }
   133  
   134  func main() {
   135  	defer exit.Recover()
   136  	defer logutil.Flush()
   137  	pflag.StringVar(&server, "server", server, "server(s) to connect to")
   138  	// handling case of --help & -h
   139  	var help bool
   140  	pflag.BoolVarP(&help, "help", "h", false, "display usage and exit")
   141  	log.RegisterFlags(pflag.CommandLine)
   142  	logutil.RegisterFlags(pflag.CommandLine)
   143  	acl.RegisterFlags(pflag.CommandLine)
   144  	pflag.CommandLine.Usage = func() {
   145  		fmt.Fprint(os.Stderr, doc)
   146  		pflag.Usage()
   147  	}
   148  
   149  	pflag.Parse()
   150  	logutil.PurgeLogs()
   151  
   152  	if help || pflag.Arg(0) == "help" {
   153  		pflag.Usage()
   154  		os.Exit(0)
   155  	}
   156  
   157  	// if no zk command is provided after --server then we need to print doc & usage both
   158  	args := pflag.Args()
   159  	if len(args) == 0 {
   160  		pflag.CommandLine.Usage()
   161  		exit.Return(1)
   162  	}
   163  	cmdName := args[0]
   164  	args = args[1:]
   165  	cmd, ok := cmdMap[cmdName]
   166  	if !ok {
   167  		log.Exitf("Unknown command %v", cmdName)
   168  	}
   169  	subFlags := pflag.NewFlagSet(cmdName, pflag.ContinueOnError)
   170  
   171  	// Create a context for the command, cancel it if we get a signal.
   172  	ctx, cancel := context.WithCancel(context.Background())
   173  	sigRecv := make(chan os.Signal, 1)
   174  	signal.Notify(sigRecv, os.Interrupt)
   175  	go func() {
   176  		<-sigRecv
   177  		cancel()
   178  	}()
   179  
   180  	// Connect to the server.
   181  	zconn = zk2topo.Connect(server)
   182  
   183  	// Run the command.
   184  	if err := cmd(ctx, subFlags, args); err != nil {
   185  		log.Error(err)
   186  		exit.Return(1)
   187  	}
   188  }
   189  
   190  func fixZkPath(zkPath string) string {
   191  	if zkPath != "/" {
   192  		zkPath = strings.TrimSuffix(zkPath, "/")
   193  	}
   194  	return path.Clean(zkPath)
   195  }
   196  
   197  func isZkFile(path string) bool {
   198  	return strings.HasPrefix(path, "/zk")
   199  }
   200  
   201  func cmdWait(ctx context.Context, subFlags *pflag.FlagSet, args []string) error {
   202  	var exitIfExists bool
   203  	subFlags.BoolVarP(&exitIfExists, "exit", "e", false, "exit if the path already exists")
   204  
   205  	if err := subFlags.Parse(args); err != nil {
   206  		return err
   207  	}
   208  
   209  	if subFlags.NArg() != 1 {
   210  		return fmt.Errorf("wait: can only wait for one path")
   211  	}
   212  	zkPath := subFlags.Arg(0)
   213  	isDir := zkPath[len(zkPath)-1] == '/'
   214  	zkPath = fixZkPath(zkPath)
   215  
   216  	var wait <-chan zk.Event
   217  	var err error
   218  	if isDir {
   219  		_, _, wait, err = zconn.ChildrenW(ctx, zkPath)
   220  	} else {
   221  		_, _, wait, err = zconn.GetW(ctx, zkPath)
   222  	}
   223  	if err != nil {
   224  		if err == zk.ErrNoNode {
   225  			_, _, wait, _ = zconn.ExistsW(ctx, zkPath)
   226  		} else {
   227  			return fmt.Errorf("wait: error %v: %v", zkPath, err)
   228  		}
   229  	} else {
   230  		if exitIfExists {
   231  			return fmt.Errorf("already exists: %v", zkPath)
   232  		}
   233  	}
   234  	event := <-wait
   235  	fmt.Printf("event: %v\n", event)
   236  	return nil
   237  }
   238  
   239  // Watch for changes to the node.
   240  func cmdWatch(ctx context.Context, subFlags *pflag.FlagSet, args []string) error {
   241  	if err := subFlags.Parse(args); err != nil {
   242  		return err
   243  	}
   244  
   245  	eventChan := make(chan zk.Event, 16)
   246  	for _, arg := range subFlags.Args() {
   247  		zkPath := fixZkPath(arg)
   248  		_, _, watch, err := zconn.GetW(ctx, zkPath)
   249  		if err != nil {
   250  			return fmt.Errorf("watch error: %v", err)
   251  		}
   252  		go func() {
   253  			eventChan <- <-watch
   254  		}()
   255  	}
   256  
   257  	for {
   258  		select {
   259  		case <-ctx.Done():
   260  			return nil
   261  		case event := <-eventChan:
   262  			log.Infof("watch: event %v: %v", event.Path, event)
   263  			if event.Type == zk.EventNodeDataChanged {
   264  				data, stat, watch, err := zconn.GetW(ctx, event.Path)
   265  				if err != nil {
   266  					return fmt.Errorf("ERROR: failed to watch %v", err)
   267  				}
   268  				log.Infof("watch: %v %v\n", event.Path, stat)
   269  				println(data)
   270  				go func() {
   271  					eventChan <- <-watch
   272  				}()
   273  			} else if event.State == zk.StateDisconnected {
   274  				return nil
   275  			} else if event.Type == zk.EventNodeDeleted {
   276  				log.Infof("watch: %v deleted\n", event.Path)
   277  			} else {
   278  				// Most likely a session event - try t
   279  				_, _, watch, err := zconn.GetW(ctx, event.Path)
   280  				if err != nil {
   281  					return fmt.Errorf("ERROR: failed to watch %v", err)
   282  				}
   283  				go func() {
   284  					eventChan <- <-watch
   285  				}()
   286  			}
   287  		}
   288  	}
   289  }
   290  
   291  func cmdLs(ctx context.Context, subFlags *pflag.FlagSet, args []string) error {
   292  	var (
   293  		longListing      bool
   294  		directoryListing bool
   295  		force            bool
   296  		recursiveListing bool
   297  	)
   298  	subFlags.BoolVarP(&longListing, "longlisting", "l", false, "long listing")
   299  	subFlags.BoolVarP(&directoryListing, "directorylisting", "d", false, "list directory instead of contents")
   300  	subFlags.BoolVarP(&force, "force", "f", false, "no warning on nonexistent node")
   301  	subFlags.BoolVarP(&recursiveListing, "recursivelisting", "R", false, "recursive listing")
   302  
   303  	if err := subFlags.Parse(args); err != nil {
   304  		return err
   305  	}
   306  	if subFlags.NArg() == 0 {
   307  		return fmt.Errorf("ls: no path specified")
   308  	}
   309  	resolved, err := zk2topo.ResolveWildcards(ctx, zconn, subFlags.Args())
   310  	if err != nil {
   311  		return fmt.Errorf("ls: invalid wildcards: %v", err)
   312  	}
   313  	if len(resolved) == 0 {
   314  		// the wildcards didn't result in anything, we're
   315  		// done.
   316  		return nil
   317  	}
   318  
   319  	hasError := false
   320  	needsHeader := len(resolved) > 1 && !directoryListing
   321  	for _, arg := range resolved {
   322  		zkPath := fixZkPath(arg)
   323  		var children []string
   324  		var err error
   325  		isDir := true
   326  		if directoryListing {
   327  			children = []string{""}
   328  			isDir = false
   329  		} else if recursiveListing {
   330  			children, err = zk2topo.ChildrenRecursive(ctx, zconn, zkPath)
   331  		} else {
   332  			children, _, err = zconn.Children(ctx, zkPath)
   333  			// Assume this is a file node if it has no children.
   334  			if len(children) == 0 {
   335  				children = []string{""}
   336  				isDir = false
   337  			}
   338  		}
   339  		if err != nil {
   340  			hasError = true
   341  			if !force || err != zk.ErrNoNode {
   342  				log.Warningf("ls: cannot access %v: %v", zkPath, err)
   343  			}
   344  		}
   345  
   346  		// Show the full path when it helps.
   347  		showFullPath := false
   348  		if recursiveListing {
   349  			showFullPath = true
   350  		} else if longListing && (directoryListing || !isDir) {
   351  			showFullPath = true
   352  		}
   353  		if needsHeader {
   354  			fmt.Printf("%v:\n", zkPath)
   355  		}
   356  		if len(children) > 0 {
   357  			if longListing && isDir {
   358  				fmt.Printf("total: %v\n", len(children))
   359  			}
   360  			sort.Strings(children)
   361  			stats := make([]*zk.Stat, len(children))
   362  			wg := sync.WaitGroup{}
   363  			f := func(i int) {
   364  				localPath := path.Join(zkPath, children[i])
   365  				_, stat, err := zconn.Exists(ctx, localPath)
   366  				if err != nil {
   367  					if !force || err != zk.ErrNoNode {
   368  						log.Warningf("ls: cannot access: %v: %v", localPath, err)
   369  					}
   370  				} else {
   371  					stats[i] = stat
   372  				}
   373  				wg.Done()
   374  			}
   375  			for i := range children {
   376  				wg.Add(1)
   377  				go f(i)
   378  			}
   379  			wg.Wait()
   380  
   381  			for i, child := range children {
   382  				localPath := path.Join(zkPath, child)
   383  				if stat := stats[i]; stat != nil {
   384  					fmtPath(stat, localPath, showFullPath, longListing)
   385  				}
   386  			}
   387  		}
   388  		if needsHeader {
   389  			fmt.Println()
   390  		}
   391  	}
   392  	if hasError {
   393  		return fmt.Errorf("ls: some paths had errors")
   394  	}
   395  	return nil
   396  }
   397  
   398  func fmtPath(stat *zk.Stat, zkPath string, showFullPath bool, longListing bool) {
   399  	var name, perms string
   400  
   401  	if !showFullPath {
   402  		name = path.Base(zkPath)
   403  	} else {
   404  		name = zkPath
   405  	}
   406  
   407  	if longListing {
   408  		if stat.NumChildren > 0 {
   409  			// FIXME(msolomon) do permissions check?
   410  			perms = "drwxrwxrwx"
   411  			if stat.DataLength > 0 {
   412  				// give a visual indication that this node has data as well as children
   413  				perms = "nrw-rw-rw-"
   414  			}
   415  		} else if stat.EphemeralOwner != 0 {
   416  			perms = "erw-rw-rw-"
   417  		} else {
   418  			perms = "-rw-rw-rw-"
   419  		}
   420  		// always print the Local version of the time. zookeeper's
   421  		// go / C library would return a local time anyway, but
   422  		// might as well be sure.
   423  		fmt.Printf("%v %v %v % 8v % 20v %v\n", perms, "zk", "zk", stat.DataLength, zk2topo.Time(stat.Mtime).Local().Format(timeFmt), name)
   424  	} else {
   425  		fmt.Printf("%v\n", name)
   426  	}
   427  }
   428  
   429  func cmdTouch(ctx context.Context, subFlags *pflag.FlagSet, args []string) error {
   430  	var (
   431  		createParents bool
   432  		touchOnly     bool
   433  	)
   434  
   435  	subFlags.BoolVarP(&createParents, "createparent", "p", false, "create parents")
   436  	subFlags.BoolVarP(&touchOnly, "touchonly", "c", false, "touch only - don't create")
   437  
   438  	if err := subFlags.Parse(args); err != nil {
   439  		return err
   440  	}
   441  	if subFlags.NArg() != 1 {
   442  		return fmt.Errorf("touch: need to specify exactly one path")
   443  	}
   444  
   445  	zkPath := fixZkPath(subFlags.Arg(0))
   446  
   447  	var (
   448  		version int32 = -1
   449  		create        = false
   450  	)
   451  
   452  	data, stat, err := zconn.Get(ctx, zkPath)
   453  	switch {
   454  	case err == nil:
   455  		version = stat.Version
   456  	case err == zk.ErrNoNode:
   457  		create = true
   458  	default:
   459  		return fmt.Errorf("touch: cannot access %v: %v", zkPath, err)
   460  	}
   461  
   462  	switch {
   463  	case !create:
   464  		_, err = zconn.Set(ctx, zkPath, data, version)
   465  	case touchOnly:
   466  		return fmt.Errorf("touch: no such path %v", zkPath)
   467  	case createParents:
   468  		_, err = zk2topo.CreateRecursive(ctx, zconn, zkPath, data, 0, zk.WorldACL(zk.PermAll), 10)
   469  	default:
   470  		_, err = zconn.Create(ctx, zkPath, data, 0, zk.WorldACL(zk.PermAll))
   471  	}
   472  
   473  	if err != nil {
   474  		return fmt.Errorf("touch: cannot modify %v: %v", zkPath, err)
   475  	}
   476  	return nil
   477  }
   478  
   479  func cmdRm(ctx context.Context, subFlags *pflag.FlagSet, args []string) error {
   480  	var (
   481  		force             bool
   482  		recursiveDelete   bool
   483  		forceAndRecursive bool
   484  	)
   485  	subFlags.BoolVarP(&force, "force", "f", false, "no warning on nonexistent node")
   486  	subFlags.BoolVarP(&recursiveDelete, "recursivedelete", "r", false, "recursive delete")
   487  	subFlags.BoolVarP(&forceAndRecursive, "forceandrecursive", "rf", false, "shorthand for -r -f")
   488  
   489  	if err := subFlags.Parse(args); err != nil {
   490  		return err
   491  	}
   492  	force = force || forceAndRecursive
   493  	recursiveDelete = recursiveDelete || forceAndRecursive
   494  
   495  	if subFlags.NArg() == 0 {
   496  		return fmt.Errorf("rm: no path specified")
   497  	}
   498  
   499  	if recursiveDelete {
   500  		for _, arg := range subFlags.Args() {
   501  			zkPath := fixZkPath(arg)
   502  			if strings.Count(zkPath, "/") < 2 {
   503  				return fmt.Errorf("rm: overly general path: %v", zkPath)
   504  			}
   505  		}
   506  	}
   507  
   508  	resolved, err := zk2topo.ResolveWildcards(ctx, zconn, subFlags.Args())
   509  	if err != nil {
   510  		return fmt.Errorf("rm: invalid wildcards: %v", err)
   511  	}
   512  	if len(resolved) == 0 {
   513  		// the wildcards didn't result in anything, we're done
   514  		return nil
   515  	}
   516  
   517  	hasError := false
   518  	for _, arg := range resolved {
   519  		zkPath := fixZkPath(arg)
   520  		var err error
   521  		if recursiveDelete {
   522  			err = zk2topo.DeleteRecursive(ctx, zconn, zkPath, -1)
   523  		} else {
   524  			err = zconn.Delete(ctx, zkPath, -1)
   525  		}
   526  		if err != nil && (!force || err != zk.ErrNoNode) {
   527  			hasError = true
   528  			log.Warningf("rm: cannot delete %v: %v", zkPath, err)
   529  		}
   530  	}
   531  	if hasError {
   532  		// to be consistent with the command line 'rm -f', return
   533  		// 0 if using 'zk rm -f' and the file doesn't exist.
   534  		return fmt.Errorf("rm: some paths had errors")
   535  	}
   536  	return nil
   537  }
   538  
   539  func cmdAddAuth(ctx context.Context, subFlags *pflag.FlagSet, args []string) error {
   540  	if err := subFlags.Parse(args); err != nil {
   541  		return err
   542  	}
   543  	if subFlags.NArg() < 2 {
   544  		return fmt.Errorf("addAuth: expected args <scheme> <auth>")
   545  	}
   546  	scheme, auth := subFlags.Arg(0), subFlags.Arg(1)
   547  	return zconn.AddAuth(ctx, scheme, []byte(auth))
   548  }
   549  
   550  func cmdCat(ctx context.Context, subFlags *pflag.FlagSet, args []string) error {
   551  	var (
   552  		longListing bool
   553  		force       bool
   554  		decodeProto bool
   555  	)
   556  	subFlags.BoolVarP(&longListing, "longListing", "l", false, "long listing")
   557  	subFlags.BoolVarP(&force, "force", "f", false, "no warning on nonexistent node")
   558  	subFlags.BoolVarP(&decodeProto, "decodeProto", "p", false, "decode proto files and display them as text")
   559  
   560  	if err := subFlags.Parse(args); err != nil {
   561  		return err
   562  	}
   563  	if subFlags.NArg() == 0 {
   564  		return fmt.Errorf("cat: no path specified")
   565  	}
   566  	resolved, err := zk2topo.ResolveWildcards(ctx, zconn, subFlags.Args())
   567  	if err != nil {
   568  		return fmt.Errorf("cat: invalid wildcards: %v", err)
   569  	}
   570  	if len(resolved) == 0 {
   571  		// the wildcards didn't result in anything, we're done
   572  		return nil
   573  	}
   574  
   575  	hasError := false
   576  	for _, arg := range resolved {
   577  		zkPath := fixZkPath(arg)
   578  		data, _, err := zconn.Get(ctx, zkPath)
   579  		if err != nil {
   580  			hasError = true
   581  			if !force || err != zk.ErrNoNode {
   582  				log.Warningf("cat: cannot access %v: %v", zkPath, err)
   583  			}
   584  			continue
   585  		}
   586  
   587  		if longListing {
   588  			fmt.Printf("%v:\n", zkPath)
   589  		}
   590  		decoded := ""
   591  		if decodeProto {
   592  			decoded, err = topo.DecodeContent(zkPath, data, false)
   593  			if err != nil {
   594  				log.Warningf("cat: cannot proto decode %v: %v", zkPath, err)
   595  				decoded = string(data)
   596  			}
   597  		} else {
   598  			decoded = string(data)
   599  		}
   600  		fmt.Print(decoded)
   601  		if len(decoded) > 0 && decoded[len(decoded)-1] != '\n' && (term.IsTerminal(int(os.Stdout.Fd())) || longListing) {
   602  			fmt.Print("\n")
   603  		}
   604  	}
   605  	if hasError {
   606  		return fmt.Errorf("cat: some paths had errors")
   607  	}
   608  	return nil
   609  }
   610  
   611  func cmdEdit(ctx context.Context, subFlags *pflag.FlagSet, args []string) error {
   612  	var force bool
   613  	subFlags.BoolVarP(&force, "force", "f", false, "no warning on nonexistent node")
   614  
   615  	if err := subFlags.Parse(args); err != nil {
   616  		return err
   617  	}
   618  	if subFlags.NArg() == 0 {
   619  		return fmt.Errorf("edit: no path specified")
   620  	}
   621  	arg := subFlags.Arg(0)
   622  	zkPath := fixZkPath(arg)
   623  	data, stat, err := zconn.Get(ctx, zkPath)
   624  	if err != nil {
   625  		if !force || err != zk.ErrNoNode {
   626  			log.Warningf("edit: cannot access %v: %v", zkPath, err)
   627  		}
   628  		return fmt.Errorf("edit: cannot access %v: %v", zkPath, err)
   629  	}
   630  
   631  	name := path.Base(zkPath)
   632  	tmpPath := fmt.Sprintf("/tmp/zk-edit-%v-%v", name, time.Now().UnixNano())
   633  	f, err := os.Create(tmpPath)
   634  	if err == nil {
   635  		_, err = f.Write(data)
   636  		f.Close()
   637  	}
   638  	if err != nil {
   639  		return fmt.Errorf("edit: cannot write file %v", err)
   640  	}
   641  
   642  	cmd := exec.Command(os.Getenv("EDITOR"), tmpPath)
   643  	cmd.Stdin = os.Stdin
   644  	cmd.Stdout = os.Stdout
   645  	cmd.Stderr = os.Stderr
   646  	err = cmd.Run()
   647  	if err != nil {
   648  		os.Remove(tmpPath)
   649  		return fmt.Errorf("edit: cannot start $EDITOR: %v", err)
   650  	}
   651  
   652  	fileData, err := os.ReadFile(tmpPath)
   653  	if err != nil {
   654  		os.Remove(tmpPath)
   655  		return fmt.Errorf("edit: cannot read file %v", err)
   656  	}
   657  
   658  	if !bytes.Equal(fileData, data) {
   659  		// data changed - update if we can
   660  		_, err = zconn.Set(ctx, zkPath, fileData, stat.Version)
   661  		if err != nil {
   662  			os.Remove(tmpPath)
   663  			return fmt.Errorf("edit: cannot write zk file %v", err)
   664  		}
   665  	}
   666  	os.Remove(tmpPath)
   667  	return nil
   668  }
   669  
   670  func cmdStat(ctx context.Context, subFlags *pflag.FlagSet, args []string) error {
   671  	var force bool
   672  	subFlags.BoolVarP(&force, "force", "f", false, "no warning on nonexistent node")
   673  
   674  	if err := subFlags.Parse(args); err != nil {
   675  		return err
   676  	}
   677  
   678  	if subFlags.NArg() == 0 {
   679  		return fmt.Errorf("stat: no path specified")
   680  	}
   681  
   682  	resolved, err := zk2topo.ResolveWildcards(ctx, zconn, subFlags.Args())
   683  	if err != nil {
   684  		return fmt.Errorf("stat: invalid wildcards: %v", err)
   685  	}
   686  	if len(resolved) == 0 {
   687  		// the wildcards didn't result in anything, we're done
   688  		return nil
   689  	}
   690  
   691  	hasError := false
   692  	for _, arg := range resolved {
   693  		zkPath := fixZkPath(arg)
   694  		acls, stat, err := zconn.GetACL(ctx, zkPath)
   695  		if stat == nil {
   696  			err = fmt.Errorf("no such node")
   697  		}
   698  		if err != nil {
   699  			hasError = true
   700  			if !force || err != zk.ErrNoNode {
   701  				log.Warningf("stat: cannot access %v: %v", zkPath, err)
   702  			}
   703  			continue
   704  		}
   705  		fmt.Printf("Path: %s\n", zkPath)
   706  		fmt.Printf("Created: %s\n", zk2topo.Time(stat.Ctime).Format(timeFmtMicro))
   707  		fmt.Printf("Modified: %s\n", zk2topo.Time(stat.Mtime).Format(timeFmtMicro))
   708  		fmt.Printf("Size: %v\n", stat.DataLength)
   709  		fmt.Printf("Children: %v\n", stat.NumChildren)
   710  		fmt.Printf("Version: %v\n", stat.Version)
   711  		fmt.Printf("Ephemeral: %v\n", stat.EphemeralOwner)
   712  		fmt.Printf("ACL:\n")
   713  		for _, acl := range acls {
   714  			fmt.Printf(" %v:%v %v\n", acl.Scheme, acl.ID, fmtACL(acl))
   715  		}
   716  	}
   717  	if hasError {
   718  		return fmt.Errorf("stat: some paths had errors")
   719  	}
   720  	return nil
   721  }
   722  
   723  var charPermMap map[string]int32
   724  var permCharMap map[int32]string
   725  
   726  func init() {
   727  	charPermMap = map[string]int32{
   728  		"r": zk.PermRead,
   729  		"w": zk.PermWrite,
   730  		"d": zk.PermDelete,
   731  		"c": zk.PermCreate,
   732  		"a": zk.PermAdmin,
   733  	}
   734  	permCharMap = make(map[int32]string)
   735  	for c, p := range charPermMap {
   736  		permCharMap[p] = c
   737  	}
   738  }
   739  
   740  func fmtACL(acl zk.ACL) string {
   741  	s := ""
   742  
   743  	for _, perm := range []int32{zk.PermRead, zk.PermWrite, zk.PermDelete, zk.PermCreate, zk.PermAdmin} {
   744  		if acl.Perms&perm != 0 {
   745  			s += permCharMap[perm]
   746  		} else {
   747  			s += "-"
   748  		}
   749  	}
   750  	return s
   751  }
   752  
   753  func cmdChmod(ctx context.Context, subFlags *pflag.FlagSet, args []string) error {
   754  	if err := subFlags.Parse(args); err != nil {
   755  		return err
   756  	}
   757  	if subFlags.NArg() < 2 {
   758  		return fmt.Errorf("chmod: no permission specified")
   759  	}
   760  	mode := subFlags.Arg(0)
   761  	if mode[0] != 'n' {
   762  		return fmt.Errorf("chmod: invalid mode")
   763  	}
   764  
   765  	addPerms := false
   766  	if mode[1] == '+' {
   767  		addPerms = true
   768  	} else if mode[1] != '-' {
   769  		return fmt.Errorf("chmod: invalid mode")
   770  	}
   771  
   772  	var permMask int32
   773  	for _, c := range mode[2:] {
   774  		permMask |= charPermMap[string(c)]
   775  	}
   776  
   777  	resolved, err := zk2topo.ResolveWildcards(ctx, zconn, subFlags.Args()[1:])
   778  	if err != nil {
   779  		return fmt.Errorf("chmod: invalid wildcards: %v", err)
   780  	}
   781  	if len(resolved) == 0 {
   782  		// the wildcards didn't result in anything, we're done
   783  		return nil
   784  	}
   785  
   786  	hasError := false
   787  	for _, arg := range resolved {
   788  		zkPath := fixZkPath(arg)
   789  		aclv, _, err := zconn.GetACL(ctx, zkPath)
   790  		if err != nil {
   791  			hasError = true
   792  			log.Warningf("chmod: cannot set access %v: %v", zkPath, err)
   793  			continue
   794  		}
   795  		if addPerms {
   796  			aclv[0].Perms |= permMask
   797  		} else {
   798  			aclv[0].Perms &= ^permMask
   799  		}
   800  		err = zconn.SetACL(ctx, zkPath, aclv, -1)
   801  		if err != nil {
   802  			hasError = true
   803  			log.Warningf("chmod: cannot set access %v: %v", zkPath, err)
   804  			continue
   805  		}
   806  	}
   807  	if hasError {
   808  		return fmt.Errorf("chmod: some paths had errors")
   809  	}
   810  	return nil
   811  }
   812  
   813  func cmdCp(ctx context.Context, subFlags *pflag.FlagSet, args []string) error {
   814  	if err := subFlags.Parse(args); err != nil {
   815  		return err
   816  	}
   817  	switch {
   818  	case subFlags.NArg() < 2:
   819  		return fmt.Errorf("cp: need to specify source and destination paths")
   820  	case subFlags.NArg() == 2:
   821  		return fileCp(ctx, args[0], args[1])
   822  	default:
   823  		return multiFileCp(ctx, args)
   824  	}
   825  }
   826  
   827  func getPathData(ctx context.Context, filePath string) ([]byte, error) {
   828  	if isZkFile(filePath) {
   829  		data, _, err := zconn.Get(ctx, filePath)
   830  		return data, err
   831  	}
   832  	var err error
   833  	file, err := os.Open(filePath)
   834  	if err == nil {
   835  		data, err := io.ReadAll(file)
   836  		if err == nil {
   837  			return data, err
   838  		}
   839  	}
   840  	return nil, err
   841  }
   842  
   843  func setPathData(ctx context.Context, filePath string, data []byte) error {
   844  	if isZkFile(filePath) {
   845  		_, err := zconn.Set(ctx, filePath, data, -1)
   846  		if err == zk.ErrNoNode {
   847  			_, err = zk2topo.CreateRecursive(ctx, zconn, filePath, data, 0, zk.WorldACL(zk.PermAll), 10)
   848  		}
   849  		return err
   850  	}
   851  	return os.WriteFile(filePath, []byte(data), 0666)
   852  }
   853  
   854  func fileCp(ctx context.Context, srcPath, dstPath string) error {
   855  	dstIsDir := dstPath[len(dstPath)-1] == '/'
   856  	srcPath = fixZkPath(srcPath)
   857  	dstPath = fixZkPath(dstPath)
   858  
   859  	if !isZkFile(srcPath) && !isZkFile(dstPath) {
   860  		return fmt.Errorf("cp: neither src nor dst is a /zk file: exitting")
   861  	}
   862  
   863  	data, err := getPathData(ctx, srcPath)
   864  	if err != nil {
   865  		return fmt.Errorf("cp: cannot read %v: %v", srcPath, err)
   866  	}
   867  
   868  	// If we are copying to a local directory - say '.', make the filename
   869  	// the same as the source.
   870  	if !isZkFile(dstPath) {
   871  		fileInfo, err := os.Stat(dstPath)
   872  		if err != nil {
   873  			if err.(*os.PathError).Err != syscall.ENOENT {
   874  				return fmt.Errorf("cp: cannot stat %v: %v", dstPath, err)
   875  			}
   876  		} else if fileInfo.IsDir() {
   877  			dstPath = path.Join(dstPath, path.Base(srcPath))
   878  		}
   879  	} else if dstIsDir {
   880  		// If we are copying into zk, interpret trailing slash as treating the
   881  		// dstPath as a directory.
   882  		dstPath = path.Join(dstPath, path.Base(srcPath))
   883  	}
   884  	if err := setPathData(ctx, dstPath, data); err != nil {
   885  		return fmt.Errorf("cp: cannot write %v: %v", dstPath, err)
   886  	}
   887  	return nil
   888  }
   889  
   890  func multiFileCp(ctx context.Context, args []string) error {
   891  	dstPath := args[len(args)-1]
   892  	if dstPath[len(dstPath)-1] != '/' {
   893  		// In multifile context, dstPath must be a directory.
   894  		dstPath += "/"
   895  	}
   896  
   897  	for _, srcPath := range args[:len(args)-1] {
   898  		if err := fileCp(ctx, srcPath, dstPath); err != nil {
   899  			return err
   900  		}
   901  	}
   902  	return nil
   903  }
   904  
   905  type zkItem struct {
   906  	path string
   907  	data []byte
   908  	stat *zk.Stat
   909  	err  error
   910  }
   911  
   912  // Store a zk tree in a zip archive. This won't be immediately useful to
   913  // zip tools since even "directories" can contain data.
   914  func cmdZip(ctx context.Context, subFlags *pflag.FlagSet, args []string) error {
   915  	if err := subFlags.Parse(args); err != nil {
   916  		return err
   917  	}
   918  	if subFlags.NArg() < 2 {
   919  		return fmt.Errorf("zip: need to specify source and destination paths")
   920  	}
   921  
   922  	dstPath := subFlags.Arg(subFlags.NArg() - 1)
   923  	paths := subFlags.Args()[:len(args)-1]
   924  	if !strings.HasSuffix(dstPath, ".zip") {
   925  		return fmt.Errorf("zip: need to specify destination .zip path: %v", dstPath)
   926  	}
   927  	zipFile, err := os.Create(dstPath)
   928  	if err != nil {
   929  		return fmt.Errorf("zip: error %v", err)
   930  	}
   931  
   932  	wg := sync.WaitGroup{}
   933  	items := make(chan *zkItem, 64)
   934  	for _, arg := range paths {
   935  		zkPath := fixZkPath(arg)
   936  		children, err := zk2topo.ChildrenRecursive(ctx, zconn, zkPath)
   937  		if err != nil {
   938  			return fmt.Errorf("zip: error %v", err)
   939  		}
   940  		for _, child := range children {
   941  			toAdd := path.Join(zkPath, child)
   942  			wg.Add(1)
   943  			go func() {
   944  				data, stat, err := zconn.Get(ctx, toAdd)
   945  				items <- &zkItem{toAdd, data, stat, err}
   946  				wg.Done()
   947  			}()
   948  		}
   949  	}
   950  	go func() {
   951  		wg.Wait()
   952  		close(items)
   953  	}()
   954  
   955  	zipWriter := zip.NewWriter(zipFile)
   956  	for item := range items {
   957  		path, data, stat, err := item.path, item.data, item.stat, item.err
   958  		if err != nil {
   959  			return fmt.Errorf("zip: get failed: %v", err)
   960  		}
   961  		// Skip ephemerals - not sure why you would archive them.
   962  		if stat.EphemeralOwner > 0 {
   963  			continue
   964  		}
   965  		fi := &zip.FileHeader{Name: path, Method: zip.Deflate}
   966  		fi.Modified = zk2topo.Time(stat.Mtime)
   967  		f, err := zipWriter.CreateHeader(fi)
   968  		if err != nil {
   969  			return fmt.Errorf("zip: create failed: %v", err)
   970  		}
   971  		_, err = f.Write(data)
   972  		if err != nil {
   973  			return fmt.Errorf("zip: create failed: %v", err)
   974  		}
   975  	}
   976  	err = zipWriter.Close()
   977  	if err != nil {
   978  		return fmt.Errorf("zip: close failed: %v", err)
   979  	}
   980  	zipFile.Close()
   981  	return nil
   982  }
   983  
   984  func cmdUnzip(ctx context.Context, subFlags *pflag.FlagSet, args []string) error {
   985  	if err := subFlags.Parse(args); err != nil {
   986  		return err
   987  	}
   988  	if subFlags.NArg() != 2 {
   989  		return fmt.Errorf("zip: need to specify source and destination paths")
   990  	}
   991  
   992  	srcPath, dstPath := subFlags.Arg(0), subFlags.Arg(1)
   993  
   994  	if !strings.HasSuffix(srcPath, ".zip") {
   995  		return fmt.Errorf("zip: need to specify src .zip path: %v", srcPath)
   996  	}
   997  
   998  	zipReader, err := zip.OpenReader(srcPath)
   999  	if err != nil {
  1000  		return fmt.Errorf("zip: error %v", err)
  1001  	}
  1002  	defer zipReader.Close()
  1003  
  1004  	for _, zf := range zipReader.File {
  1005  		rc, err := zf.Open()
  1006  		if err != nil {
  1007  			return fmt.Errorf("unzip: error %v", err)
  1008  		}
  1009  		data, err := io.ReadAll(rc)
  1010  		if err != nil {
  1011  			return fmt.Errorf("unzip: failed reading archive: %v", err)
  1012  		}
  1013  		zkPath := zf.Name
  1014  		if dstPath != "/" {
  1015  			zkPath = path.Join(dstPath, zkPath)
  1016  		}
  1017  		_, err = zk2topo.CreateRecursive(ctx, zconn, zkPath, data, 0, zk.WorldACL(zk.PermAll), 10)
  1018  		if err != nil && err != zk.ErrNodeExists {
  1019  			return fmt.Errorf("unzip: zk create failed: %v", err)
  1020  		}
  1021  		_, err = zconn.Set(ctx, zkPath, data, -1)
  1022  		if err != nil {
  1023  			return fmt.Errorf("unzip: zk set failed: %v", err)
  1024  		}
  1025  		rc.Close()
  1026  	}
  1027  	return nil
  1028  }