From 1d52ef6c542598e1c399e507310ea0adb1310304 Mon Sep 17 00:00:00 2001 From: Zachary Yedidia Date: Thu, 2 Jun 2016 13:01:13 -0400 Subject: [PATCH] Add simple way to save with sudo if you forgot to open micro with sudo If you are editing a read-only file and forgot to open micro with sudo so you could write to it, when saving the file, micro will now give you the option to save with sudo. This little hack is used by vim users to achieve the same behavior, but micro makes it nicer to use. Here is an explanation for how it works: http://stackoverflow.com/questions/2600783/how-does-the-vim-write-with-sudo-trick-work Fixes #158 --- cmd/micro/bindings.go | 19 +++++++++++--- cmd/micro/buffer.go | 58 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/cmd/micro/bindings.go b/cmd/micro/bindings.go index 2f623fb1..27fd7bd5 100644 --- a/cmd/micro/bindings.go +++ b/cmd/micro/bindings.go @@ -691,16 +691,29 @@ func (v *View) Save() bool { v.Buf.Path = filename v.Buf.Name = filename } else { - return true + return false } } err := v.Buf.Save() if err != nil { - messenger.Error(err.Error()) + if strings.HasSuffix(err.Error(), "permission denied") { + choice, _ := messenger.YesNoPrompt("Permission denied. Do you want to save this file using sudo? (y,n)") + if choice { + err = v.Buf.SaveWithSudo() + if err != nil { + messenger.Error(err.Error()) + return false + } + } + messenger.Reset() + messenger.Clear() + } else { + messenger.Error(err.Error()) + } } else { messenger.Message("Saved " + v.Buf.Path) } - return true + return false } // Find opens a prompt and searches forward for the input diff --git a/cmd/micro/buffer.go b/cmd/micro/buffer.go index 0357db52..a9451e93 100644 --- a/cmd/micro/buffer.go +++ b/cmd/micro/buffer.go @@ -1,9 +1,12 @@ package main import ( + "bytes" "encoding/gob" "io/ioutil" "os" + "os/exec" + "os/signal" "path/filepath" "strings" "time" @@ -174,6 +177,11 @@ func (b *Buffer) Save() error { return b.SaveAs(b.Path) } +// SaveWithSudo saves the buffer to the default path with sudo +func (b *Buffer) SaveWithSudo() error { + return b.SaveAsWithSudo(b.Path) +} + // Serialize serializes the buffer to configDir/buffers func (b *Buffer) Serialize() error { if settings["savecursor"].(bool) || settings["saveundo"].(bool) { @@ -210,6 +218,56 @@ func (b *Buffer) SaveAs(filename string) error { return err } +// SaveAsWithSudo is the same as SaveAs except it uses a neat trick +// with tee to use sudo so the user doesn't have to reopen micro with sudo +func (b *Buffer) SaveAsWithSudo(filename string) error { + b.UpdateRules() + b.Name = filename + b.Path = filename + + // The user may have already used sudo in which case we won't need the password + // It's a bit nicer for them if they don't have to enter the password every time + _, err := RunShellCommand("sudo -v") + needPassword := err != nil + + // If we need the password, we have to close the screen and ask using the shell + if needPassword { + // Shut down the screen because we're going to interact directly with the shell + screen.Fini() + screen = nil + } + + // Set up everything for the command + cmd := exec.Command("sudo", "tee", filename) + cmd.Stdin = bytes.NewBufferString(b.String()) + + // This is a trap for Ctrl-C so that it doesn't kill micro + // Instead we trap Ctrl-C to kill the program we're running + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + go func() { + for range c { + cmd.Process.Kill() + } + }() + + // Start the command + cmd.Start() + err = cmd.Wait() + + // If we needed the password, we closed the screen, so we have to initialize it again + if needPassword { + // Start the screen back up + InitScreen() + } + if err == nil { + b.IsModified = false + b.ModTime, _ = GetModTime(filename) + b.Serialize() + } + return err +} + // This directly inserts value at idx, bypassing all undo/redo func (b *Buffer) insert(idx int, value string) { b.IsModified = true