github.com/rigado/snapd@v2.42.5-go-mod+incompatible/overlord/hookstate/ctlcmd/ctlcmd.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 // Package ctlcmd contains the various snapctl subcommands. 21 package ctlcmd 22 23 import ( 24 "bytes" 25 "fmt" 26 "io" 27 28 "github.com/snapcore/snapd/logger" 29 "github.com/snapcore/snapd/overlord/hookstate" 30 31 "github.com/jessevdk/go-flags" 32 ) 33 34 type baseCommand struct { 35 stdout io.Writer 36 stderr io.Writer 37 c *hookstate.Context 38 } 39 40 func (c *baseCommand) setStdout(w io.Writer) { 41 c.stdout = w 42 } 43 44 func (c *baseCommand) printf(format string, a ...interface{}) { 45 if c.stdout != nil { 46 fmt.Fprintf(c.stdout, format, a...) 47 } 48 } 49 50 func (c *baseCommand) setStderr(w io.Writer) { 51 c.stderr = w 52 } 53 54 func (c *baseCommand) errorf(format string, a ...interface{}) { 55 if c.stderr != nil { 56 fmt.Fprintf(c.stderr, format, a...) 57 } 58 } 59 60 func (c *baseCommand) setContext(context *hookstate.Context) { 61 c.c = context 62 } 63 64 func (c *baseCommand) context() *hookstate.Context { 65 return c.c 66 } 67 68 type command interface { 69 setStdout(w io.Writer) 70 setStderr(w io.Writer) 71 72 setContext(context *hookstate.Context) 73 context() *hookstate.Context 74 75 Execute(args []string) error 76 } 77 78 type commandInfo struct { 79 shortHelp string 80 longHelp string 81 generator func() command 82 hidden bool 83 } 84 85 var commands = make(map[string]*commandInfo) 86 87 func addCommand(name, shortHelp, longHelp string, generator func() command) *commandInfo { 88 cmd := &commandInfo{ 89 shortHelp: shortHelp, 90 longHelp: longHelp, 91 generator: generator, 92 } 93 commands[name] = cmd 94 return cmd 95 } 96 97 // ForbiddenCommandError conveys that a command cannot be invoked in some context 98 type ForbiddenCommandError struct { 99 Message string 100 } 101 102 func (f ForbiddenCommandError) Error() string { 103 return f.Message 104 } 105 106 // ForbiddenCommand contains information about an attempt to use a command in a context where it is not allowed. 107 type ForbiddenCommand struct { 108 Uid uint32 109 Name string 110 } 111 112 func (f *ForbiddenCommand) Execute(args []string) error { 113 return &ForbiddenCommandError{Message: fmt.Sprintf("cannot use %q with uid %d, try with sudo", f.Name, f.Uid)} 114 } 115 116 // Run runs the requested command. 117 func Run(context *hookstate.Context, args []string, uid uint32) (stdout, stderr []byte, err error) { 118 parser := flags.NewParser(nil, flags.PassDoubleDash|flags.HelpFlag) 119 120 // Create stdout/stderr buffers, and make sure commands use them. 121 var stdoutBuffer bytes.Buffer 122 var stderrBuffer bytes.Buffer 123 for name, cmdInfo := range commands { 124 var data interface{} 125 // commands listed here will be allowed for regular users 126 // note: commands still need valid context and snaps can only access own config. 127 if uid == 0 || name == "get" || name == "services" || name == "set-health" { 128 cmd := cmdInfo.generator() 129 cmd.setStdout(&stdoutBuffer) 130 cmd.setStderr(&stderrBuffer) 131 cmd.setContext(context) 132 data = cmd 133 } else { 134 data = &ForbiddenCommand{Uid: uid, Name: name} 135 } 136 theCmd, err := parser.AddCommand(name, cmdInfo.shortHelp, cmdInfo.longHelp, data) 137 theCmd.Hidden = cmdInfo.hidden 138 if err != nil { 139 logger.Panicf("cannot add command %q: %s", name, err) 140 } 141 } 142 143 _, err = parser.ParseArgs(args) 144 return stdoutBuffer.Bytes(), stderrBuffer.Bytes(), err 145 }