func (x *cmdShell) Execute(args []string) error { if len(args) > 0 { return ErrExtraArgs } shellType := x.Positional.ShellType // FIXME: make this generic so that all snaps can provide a // shell if shellType == "classic" { if !osutil.FileExists("/snap/classic/current") { return fmt.Errorf(i18n.G(`Classic dimension disabled on this system. Use "sudo snap install --devmode classic && sudo classic.create" to enable it.`)) } // we need to re-exec if we do not run as root if os.Getuid() != 0 { if err := reexecWithSudo(); err != nil { return err } } fmt.Fprintln(Stdout, i18n.G(`Entering classic dimension`)) fmt.Fprintln(Stdout, i18n.G(` The home directory is shared between snappy and the classic dimension. Run "exit" to leave the classic shell. `)) args := []string{"/snap/bin/classic.shell"} return syscall.Exec(args[0], args, os.Environ()) } return fmt.Errorf(i18n.G("unsupported shell %v"), shellType) }
func snapUpdateMany(inst *snapInstruction, st *state.State) (msg string, updated []string, tasksets []*state.TaskSet, err error) { // we need refreshed snap-declarations to enforce refresh-control as best as we can, this also ensures that snap-declarations and their prerequisite assertions are updated regularly if err := assertstateRefreshSnapDeclarations(st, inst.userID); err != nil { return "", nil, nil, err } updated, tasksets, err = snapstateUpdateMany(st, inst.Snaps, inst.userID) if err != nil { return "", nil, nil, err } switch len(inst.Snaps) { case 0: // all snaps msg = i18n.G("Refresh all snaps in the system") case 1: msg = fmt.Sprintf(i18n.G("Refresh snap %q"), inst.Snaps[0]) default: quoted := make([]string, len(inst.Snaps)) for i, name := range inst.Snaps { quoted[i] = strconv.Quote(name) } // TRANSLATORS: the %s is a comma-separated list of quoted snap names msg = fmt.Sprintf(i18n.G("Refresh snaps %s"), strings.Join(quoted, ", ")) } return msg, updated, tasksets, nil }
func main() { cmd.ExecInCoreSnap() // magic \o/ snapApp := filepath.Base(os.Args[0]) if osutil.IsSymlink(filepath.Join(dirs.SnapBinariesDir, snapApp)) { cmd := &cmdRun{} args := []string{snapApp} args = append(args, os.Args[1:]...) // this will call syscall.Exec() so it does not return // *unless* there is an error, i.e. we setup a wrong // symlink (or syscall.Exec() fails for strange reasons) err := cmd.Execute(args) fmt.Fprintf(Stderr, i18n.G("internal error, please report: running %q failed: %v\n"), snapApp, err) os.Exit(46) } defer func() { if v := recover(); v != nil { if e, ok := v.(*exitStatus); ok { os.Exit(e.code) } panic(v) } }() // no magic /o\ if err := run(); err != nil { fmt.Fprintf(Stderr, i18n.G("error: %v\n"), err) os.Exit(1) } }
func (x *cmdRun) Execute(args []string) error { if len(args) == 0 { return fmt.Errorf(i18n.G("need the application to run as argument")) } snapApp := args[0] args = args[1:] // Catch some invalid parameter combinations, provide helpful errors if x.Hook != "" && x.Command != "" { return fmt.Errorf(i18n.G("cannot use --hook and --command together")) } if x.Revision != "unset" && x.Revision != "" && x.Hook == "" { return fmt.Errorf(i18n.G("-r can only be used with --hook")) } if x.Hook != "" && len(args) > 0 { // TRANSLATORS: %q is the hook name; %s a space-separated list of extra arguments return fmt.Errorf(i18n.G("too many arguments for hook %q: %s"), x.Hook, strings.Join(args, " ")) } // Now actually handle the dispatching if x.Hook != "" { return snapRunHook(snapApp, x.Revision, x.Hook) } // pass shell as a special command to snap-exec if x.Shell { x.Command = "shell" } return snapRunApp(snapApp, x.Command, args) }
func (x *cmdBooted) Execute(args []string) error { if len(args) > 0 { return ErrExtraArgs } if release.OnClassic { fmt.Fprintf(Stdout, i18n.G("Ignoring 'booted' on classic")) return nil } bootloader, err := partition.FindBootloader() if err != nil { return fmt.Errorf(i18n.G("cannot mark boot successful: %s"), err) } if err := partition.MarkBootSuccessful(bootloader); err != nil { return err } ovld, err := overlord.New() if err != nil { return err } return boot.UpdateRevisions(ovld) }
// Disable sets a snap to the inactive state func Disable(s *state.State, name string) (*state.TaskSet, error) { var snapst SnapState err := Get(s, name, &snapst) if err == state.ErrNoState { return nil, fmt.Errorf("cannot find snap %q", name) } if err != nil { return nil, err } if !snapst.Active { return nil, fmt.Errorf("snap %q already disabled", name) } if err := checkChangeConflict(s, name, nil); err != nil { return nil, err } ss := &SnapSetup{ SideInfo: &snap.SideInfo{ RealName: name, Revision: snapst.Current, }, } stopSnapServices := s.NewTask("stop-snap-services", fmt.Sprintf(i18n.G("Stop snap %q (%s) services"), ss.Name(), snapst.Current)) stopSnapServices.Set("snap-setup", &ss) unlinkSnap := s.NewTask("unlink-snap", fmt.Sprintf(i18n.G("Make snap %q (%s) unavailable to the system"), ss.Name(), snapst.Current)) unlinkSnap.Set("snap-setup-task", stopSnapServices.ID()) unlinkSnap.WaitFor(stopSnapServices) return state.NewTaskSet(stopSnapServices, unlinkSnap), nil }
func listSnaps(names []string, all bool) error { cli := Client() snaps, err := cli.List(names, &client.ListOptions{All: all}) if err != nil { if err == client.ErrNoSnapsInstalled { fmt.Fprintln(Stderr, i18n.G("No snaps are installed yet. Try \"snap install hello-world\".")) return nil } return err } else if len(snaps) == 0 { return errors.New(i18n.G("no matching snaps installed")) } sort.Sort(snapsByName(snaps)) w := tabWriter() defer w.Flush() fmt.Fprintln(w, i18n.G("Name\tVersion\tRev\tDeveloper\tNotes")) for _, snap := range snaps { // TODO: make JailMode a flag in the snap itself fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", snap.Name, snap.Version, snap.Revision, snap.Developer, NotesFromLocal(snap)) } return nil }
func init() { addCommand("find", shortFindHelp, longFindHelp, func() flags.Commander { return &cmdFind{} }, map[string]string{ "private": i18n.G("Search private snaps"), }, []argDesc{{name: i18n.G("<query>")}}) }
func (x *cmdRefresh) Execute([]string) error { if err := x.setChannelFromCommandline(); err != nil { return err } if err := x.validateMode(); err != nil { return err } if x.List { if x.asksForMode() || x.asksForChannel() { return errors.New(i18n.G("--list does not take mode nor channel flags")) } return listRefresh() } if len(x.Positional.Snaps) == 1 { opts := &client.SnapOptions{ Channel: x.Channel, DevMode: x.DevMode, JailMode: x.JailMode, Revision: x.Revision, } return refreshOne(x.Positional.Snaps[0], opts) } if x.asksForMode() || x.asksForChannel() { return errors.New(i18n.G("a single snap name is needed to specify mode or channel flags")) } return refreshMany(x.Positional.Snaps, nil) }
// show what has been done func showDone(names []string, op string) error { cli := Client() snaps, err := cli.List(names, nil) if err != nil { return err } for _, snap := range snaps { channelStr := "" if snap.Channel != "" && snap.Channel != "stable" { channelStr = fmt.Sprintf(" (%s)", snap.Channel) } switch op { case "install": if snap.Developer != "" { fmt.Fprintf(Stdout, i18n.G("%s%s %s from '%s' installed\n"), snap.Name, channelStr, snap.Version, snap.Developer) } else { fmt.Fprintf(Stdout, i18n.G("%s%s %s installed\n"), snap.Name, channelStr, snap.Version) } case "refresh": if snap.Developer != "" { fmt.Fprintf(Stdout, i18n.G("%s%s %s from '%s' refreshed\n"), snap.Name, channelStr, snap.Version, snap.Developer) } else { fmt.Fprintf(Stdout, i18n.G("%s%s %s refreshed\n"), snap.Name, channelStr, snap.Version) } default: fmt.Fprintf(Stdout, "internal error, unknown op %q", op) } } return nil }
func requestLoginWith2faRetry(email, password string) error { var otp []byte var err error var msgs = [3]string{ i18n.G("Two-factor code: "), i18n.G("Bad code. Try again: "), i18n.G("Wrong again. Once more: "), } cli := Client() reader := bufio.NewReader(nil) for i := 0; ; i++ { // first try is without otp _, err = cli.Login(email, password, string(otp)) if i >= len(msgs) || !client.IsTwoFactorError(err) { return err } reader.Reset(Stdin) fmt.Fprint(Stdout, msgs[i]) // the browser shows it as well (and Sergio wants to see it ;) otp, _, err = reader.ReadLine() if err != nil { return err } } }
// Enable sets a snap to the active state func Enable(s *state.State, name string) (*state.TaskSet, error) { var snapst SnapState err := Get(s, name, &snapst) if err == state.ErrNoState { return nil, fmt.Errorf("cannot find snap %q", name) } if err != nil { return nil, err } if snapst.Active { return nil, fmt.Errorf("snap %q already enabled", name) } if err := checkChangeConflict(s, name, nil); err != nil { return nil, err } ss := &SnapSetup{ SideInfo: snapst.CurrentSideInfo(), } prepareSnap := s.NewTask("prepare-snap", fmt.Sprintf(i18n.G("Prepare snap %q (%s)"), ss.Name(), snapst.Current)) prepareSnap.Set("snap-setup", &ss) linkSnap := s.NewTask("link-snap", fmt.Sprintf(i18n.G("Make snap %q (%s) available to the system"), ss.Name(), snapst.Current)) linkSnap.Set("snap-setup", &ss) linkSnap.WaitFor(prepareSnap) startSnapServices := s.NewTask("start-snap-services", fmt.Sprintf(i18n.G("Start snap %q (%s) services"), ss.Name(), snapst.Current)) startSnapServices.Set("snap-setup", &ss) startSnapServices.WaitFor(linkSnap) return state.NewTaskSet(prepareSnap, linkSnap, startSnapServices), nil }
func listRefresh() error { cli := Client() snaps, _, err := cli.Find(&client.FindOptions{ Refresh: true, }) if err != nil { return err } if len(snaps) == 0 { fmt.Fprintln(Stderr, i18n.G("All snaps up to date.")) return nil } sort.Sort(snapsByName(snaps)) w := tabWriter() defer w.Flush() fmt.Fprintln(w, i18n.G("Name\tVersion\tRev\tDeveloper\tNotes")) for _, snap := range snaps { fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", snap.Name, snap.Version, snap.Revision, snap.Developer, NotesFromRemote(snap, nil)) } return nil }
func (x *cmdRevert) Execute(args []string) error { if len(args) > 0 { return ErrExtraArgs } if err := x.validateMode(); err != nil { return err } cli := Client() name := string(x.Positional.Snap) opts := &client.SnapOptions{DevMode: x.DevMode, JailMode: x.JailMode, Revision: x.Revision} changeID, err := cli.Revert(name, opts) if err != nil { return err } if _, err := wait(cli, changeID); err != nil { return err } // show output as speced snaps, err := cli.List([]string{name}, nil) if err != nil { return err } if len(snaps) != 1 { // TRANSLATORS: %q gets the snap name, %v the list of things found when trying to list it return fmt.Errorf(i18n.G("cannot get data for %q: %v"), name, snaps) } snap := snaps[0] fmt.Fprintf(Stdout, i18n.G("%s reverted to %s\n"), name, snap.Version) return nil }
func run() error { parser := Parser() _, err := parser.Parse() if err != nil { if e, ok := err.(*flags.Error); ok { if e.Type == flags.ErrHelp { if parser.Command.Active != nil && parser.Command.Active.Name == "help" { parser.Command.Active = nil } parser.WriteHelp(Stdout) return nil } if e.Type == flags.ErrUnknownCommand { return fmt.Errorf(i18n.G(`unknown command %q, see "snap --help"`), os.Args[1]) } } if e, ok := err.(*client.Error); ok && e.Kind == client.ErrorKindLoginRequired { u, _ := user.Current() if u != nil && u.Username == "root" { return fmt.Errorf(i18n.G(`%s (see "snap login --help")`), e.Message) } // TRANSLATORS: %s will be a message along the lines of "login required" return fmt.Errorf(i18n.G(`%s (try with sudo)`), e.Message) } } return err }
func findSnaps(opts *client.FindOptions) error { cli := Client() snaps, resInfo, err := cli.Find(opts) if err != nil { return err } if len(snaps) == 0 { // TRANSLATORS: the %q is the (quoted) query the user entered return fmt.Errorf(i18n.G("no snaps found for %q"), opts.Query) } w := tabWriter() defer w.Flush() fmt.Fprintln(w, i18n.G("Name\tVersion\tDeveloper\tNotes\tSummary")) for _, snap := range snaps { notes := &Notes{ Private: snap.Private, DevMode: snap.Confinement != client.StrictConfinement, Price: getPriceString(snap.Prices, resInfo.SuggestedCurrency, snap.Status), } // TODO: get snap.Publisher, so we can only show snap.Developer if it's different fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", snap.Name, snap.Version, snap.Developer, notes, snap.Summary) } return nil }
func (x *cmdCreateKey) Execute(args []string) error { if len(args) > 0 { return ErrExtraArgs } keyName := x.Positional.KeyName if keyName == "" { keyName = "default" } if !asserts.IsValidAccountKeyName(keyName) { return fmt.Errorf(i18n.G("key name %q is not valid; only ASCII letters, digits, and hyphens are allowed"), keyName) } fmt.Fprint(Stdout, i18n.G("Passphrase: ")) passphrase, err := terminal.ReadPassword(0) fmt.Fprint(Stdout, "\n") if err != nil { return err } fmt.Fprint(Stdout, i18n.G("Confirm passphrase: ")) confirmPassphrase, err := terminal.ReadPassword(0) fmt.Fprint(Stdout, "\n") if err != nil { return err } if string(passphrase) != string(confirmPassphrase) { return errors.New("passphrases do not match") } if err != nil { return err } manager := asserts.NewGPGKeypairManager() return manager.Generate(string(passphrase), keyName) }
// patch3: // - migrates pending tasks and add {start,stop}-snap-services tasks func patch3(s *state.State) error { // migrate all pending tasks and insert "{start,stop}-snap-server" for _, t := range s.Tasks() { if t.Status().Ready() { continue } if t.Kind() == "link-snap" { startSnapServices := s.NewTask("start-snap-services", fmt.Sprintf(i18n.G("Start snap services"))) startSnapServices.Set("snap-setup-task", t.ID()) startSnapServices.WaitFor(t) chg := t.Change() chg.AddTask(startSnapServices) } if t.Kind() == "unlink-snap" || t.Kind() == "unlink-current-snap" { stopSnapServices := s.NewTask("stop-snap-services", fmt.Sprintf(i18n.G("Stop snap services"))) stopSnapServices.Set("snap-setup-task", t.ID()) t.WaitFor(stopSnapServices) chg := t.Change() chg.AddTask(stopSnapServices) } } return nil }
func init() { addCommand("disconnect", shortDisconnectHelp, longDisconnectHelp, func() flags.Commander { return &cmdDisconnect{} }, nil, []argDesc{ {name: i18n.G("<snap>:<plug>")}, {name: i18n.G("<snap>:<slot>")}, }) }
func init() { addCommand("ack", shortAckHelp, longAckHelp, func() flags.Commander { return &cmdAck{} }, nil, []argDesc{{ name: i18n.G("<assertion file>"), desc: i18n.G("Assertion file"), }}) }
func init() { addCommand("alias", shortAliasHelp, longAliasHelp, func() flags.Commander { return &cmdAlias{} }, nil, []argDesc{ {name: i18n.G("<snap>")}, {name: i18n.G("<alias>")}, }) }
func init() { cmd := addCommand("keys", i18n.G("List cryptographic keys"), i18n.G("List cryptographic keys that can be used for signing assertions."), func() flags.Commander { return &cmdKeys{} }, map[string]string{"json": i18n.G("Output results in JSON format")}, nil) cmd.hidden = true }
func init() { addCommand("download", shortDownloadHelp, longDownloadHelp, func() flags.Commander { return &cmdDownload{} }, channelDescs.also(map[string]string{ "revision": i18n.G("Download the given revision of a snap, to which you must have developer access"), }), []argDesc{{ name: "<snap>", desc: i18n.G("Snap name"), }}) }
func init() { addCommand("interfaces", shortInterfacesHelp, longInterfacesHelp, func() flags.Commander { return &cmdInterfaces{} }, map[string]string{ "i": i18n.G("Constrain listing to specific interfaces"), }, []argDesc{{ name: i18n.G("<snap>:<slot or plug>"), desc: i18n.G("Constrain listing to a specific snap or snap:name"), }}) }
func init() { addCommand("alias", shortAliasHelp, longAliasHelp, func() flags.Commander { return &cmdAlias{} }, map[string]string{ "reset": i18n.G("Reset the aliases to their default state, enabled for automatic aliases, disabled otherwise"), }, []argDesc{ {name: "<snap>"}, {name: i18n.G("<alias>")}, }) }
func init() { addCommand("buy", shortBuyHelp, longBuyHelp, func() flags.Commander { return &cmdBuy{} }, map[string]string{ "currency": i18n.G("ISO 4217 code for currency (https://en.wikipedia.org/wiki/ISO_4217)"), }, []argDesc{{ name: "<snap>", desc: i18n.G("Snap name"), }}) }
func init() { addCommand("set", shortSetHelp, longSetHelp, func() flags.Commander { return &cmdSet{} }, nil, []argDesc{ { name: "<snap>", desc: i18n.G("The snap to configure (e.g. hello-world)"), }, { name: i18n.G("<conf value>"), desc: i18n.G("Configuration value (key=value)"), }, }) }
func init() { cmd := addCommand("delete-key", i18n.G("Delete cryptographic key pair"), i18n.G("Delete the local cryptographic key pair with the given name."), func() flags.Commander { return &cmdDeleteKey{} }, nil, []argDesc{{ name: i18n.G("<key-name>"), desc: i18n.G("Name of key to delete"), }}) cmd.hidden = true }
func init() { cmd := addCommand("auto-import", shortAutoImportHelp, longAutoImportHelp, func() flags.Commander { return &cmdAutoImport{} }, map[string]string{ "mount": i18n.G("Temporarily mount device before inspecting"), "force-classic": i18n.G("Force import on classic systems"), }, nil) cmd.hidden = true }
func init() { addCommand("login", shortLoginHelp, longLoginHelp, func() flags.Commander { return &cmdLogin{} }, nil, []argDesc{{ // TRANSLATORS: noun name: i18n.G("<email>"), desc: i18n.G("The login.ubuntu.com email to login as"), }}) }