Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

save: Perform write process safe #3273

Open
wants to merge 30 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e994372
actions: SaveAs: Print the error of `os.Stat()` to the `InfoBar`
JoeKar Apr 29, 2024
07e976c
save: Convert `os.IsNotExist()` into `errors.Is()`
JoeKar May 1, 2024
e60a521
open & write: Process regular files only
JoeKar May 12, 2024
8dc087c
buffer: Convert `os.Is()` into `errors.Is()`
JoeKar May 29, 2024
ce85c1d
backup: Convert `os.IsNotExist()` into `errors.Is()`
JoeKar May 12, 2024
aaa2d1f
backup: Store the file with the endings of the buffer
JoeKar May 23, 2024
668fa69
backup: Lock the buffer lines in `Backup()`
JoeKar May 24, 2024
583eb26
bindings: Convert `os.IsNotExist()` into `errors.Is()`
JoeKar May 12, 2024
7295e72
clean: Inform about all failed write steps
JoeKar May 12, 2024
6ba3332
clean: Remove some unneeded `filepath.Join()` calls
JoeKar May 12, 2024
505179d
util: Improve and rename `EscapePath()` to `DetermineEscapePath()`
JoeKar May 24, 2024
c7af27a
util: Generalize the file mode of 0666 with `util.FileMode`
JoeKar May 30, 2024
85e84b3
ioutil: Remove deprecated functions where possible
JoeKar May 30, 2024
a4c1f8c
save: Perform write process safe
JoeKar May 29, 2024
f0d3cf1
actions: Don't overwrite the buffers `Path`
JoeKar Oct 1, 2024
aeeba54
util: Provide `AppendBackupSuffix()` for further transformations
JoeKar Sep 3, 2024
5b79fed
backup: Perform write process safe
JoeKar May 31, 2024
298cf28
util: Provide `SafeWrite()` to generalize the internal file write pro…
JoeKar Aug 29, 2024
68c6dce
serialize: Perform write process safe
JoeKar Jun 1, 2024
2ed7410
bindings: Perform write process safe
JoeKar Jun 1, 2024
ec2c916
settings: Perform write process safe
JoeKar Jun 1, 2024
84b4465
save: Merge `overwrite()` into `overwriteFile()` and extract `writeFi…
JoeKar Oct 2, 2024
a9d71ff
save: Further rework of `overwriteFile()`
JoeKar Nov 19, 2024
20af550
micro: Generalize exit behavior
JoeKar Sep 8, 2024
47ecc91
micro: Provide recovery of `settings.json` & `bindings.json`
JoeKar Sep 8, 2024
7e4d0a5
backup: Rearrange and extend `BackupMsg`
JoeKar Oct 30, 2024
024a051
buffer: Remove superfluous `backupTime`
JoeKar Oct 12, 2024
4a10d28
save+backup: Process the `save` & `backup` with a sequential channel
JoeKar Oct 1, 2024
674ad80
backup: Clear the requested backup upon completion notification
JoeKar Oct 12, 2024
c1989a8
save+util: Provide a meaningful error message for safe (over-)write f…
JoeKar Nov 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 24 additions & 9 deletions cmd/micro/clean.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ package main
import (
"bufio"
JoeKar marked this conversation as resolved.
Show resolved Hide resolved
"encoding/gob"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"

"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/util"
)

func shouldContinue() bool {
Expand Down Expand Up @@ -39,7 +40,16 @@ func CleanConfig() {
}

fmt.Println("Cleaning default settings")
config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))

settingsFile := filepath.Join(config.ConfigDir, "settings.json")
err := config.WriteSettings(settingsFile)
if err != nil {
if errors.Is(err, util.ErrOverwrite) {
fmt.Println(err.Error())
} else {
fmt.Println("Error writing settings.json file: " + err.Error())
}
}

// detect unused options
var unusedOptions []string
Expand Down Expand Up @@ -67,16 +77,20 @@ func CleanConfig() {
fmt.Printf("%s (value: %v)\n", s, config.GlobalSettings[s])
}

fmt.Printf("These options will be removed from %s\n", filepath.Join(config.ConfigDir, "settings.json"))
fmt.Printf("These options will be removed from %s\n", settingsFile)

if shouldContinue() {
for _, s := range unusedOptions {
delete(config.GlobalSettings, s)
}

err := config.OverwriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
err := config.OverwriteSettings(settingsFile)
if err != nil {
fmt.Println("Error writing settings.json file: " + err.Error())
if errors.Is(err, util.ErrOverwrite) {
fmt.Println(err.Error())
} else {
fmt.Println("Error overwriting settings.json file: " + err.Error())
}
}

fmt.Println("Removed unused options")
Expand All @@ -85,12 +99,13 @@ func CleanConfig() {
}

// detect incorrectly formatted buffer/ files
files, err := ioutil.ReadDir(filepath.Join(config.ConfigDir, "buffers"))
buffersPath := filepath.Join(config.ConfigDir, "buffers")
files, err := os.ReadDir(buffersPath)
if err == nil {
var badFiles []string
var buffer buffer.SerializedBuffer
for _, f := range files {
fname := filepath.Join(config.ConfigDir, "buffers", f.Name())
fname := filepath.Join(buffersPath, f.Name())
file, e := os.Open(fname)

if e == nil {
Expand All @@ -105,9 +120,9 @@ func CleanConfig() {
}

if len(badFiles) > 0 {
fmt.Printf("Detected %d files with an invalid format in %s\n", len(badFiles), filepath.Join(config.ConfigDir, "buffers"))
fmt.Printf("Detected %d files with an invalid format in %s\n", len(badFiles), buffersPath)
fmt.Println("These files store cursor and undo history.")
fmt.Printf("Removing badly formatted files in %s\n", filepath.Join(config.ConfigDir, "buffers"))
fmt.Printf("Removing badly formatted files in %s\n", buffersPath)

if shouldContinue() {
removed := 0
Expand Down
2 changes: 1 addition & 1 deletion cmd/micro/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func (NullWriter) Write(data []byte) (n int, err error) {
// InitLog sets up the debug log system for micro if it has been enabled by compile-time variables
func InitLog() {
if util.Debug == "ON" {
f, err := os.OpenFile("log.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
f, err := os.OpenFile("log.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, util.FileMode)
if err != nil {
log.Fatalf("error opening file: %v", err)
}
Expand Down
102 changes: 68 additions & 34 deletions cmd/micro/micro.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import (
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/signal"
"path/filepath"
"regexp"
"runtime"
"runtime/pprof"
Expand Down Expand Up @@ -99,7 +99,7 @@ func InitFlags() {
fmt.Println("Version:", util.Version)
fmt.Println("Commit hash:", util.CommitHash)
fmt.Println("Compiled on", util.CompileDate)
os.Exit(0)
exit(0)
}

if *flagOptions {
Expand All @@ -115,7 +115,7 @@ func InitFlags() {
fmt.Printf("-%s value\n", k)
fmt.Printf(" \tDefault value: '%v'\n", v)
}
os.Exit(0)
exit(0)
}

if util.Debug == "OFF" && *flagDebug {
Expand All @@ -136,7 +136,7 @@ func DoPluginFlags() {
CleanConfig()
}

os.Exit(0)
exit(0)
}
}

Expand Down Expand Up @@ -209,7 +209,7 @@ func LoadInput(args []string) []*buffer.Buffer {
// Option 2
// The input is not a terminal, so something is being piped in
// and we should read from stdin
input, err = ioutil.ReadAll(os.Stdin)
input, err = io.ReadAll(os.Stdin)
if err != nil {
screen.TermMessage("Error reading from stdin: ", err)
input = []byte{}
Expand All @@ -223,12 +223,55 @@ func LoadInput(args []string) []*buffer.Buffer {
return buffers
}

func checkBackup(name string) error {
target := filepath.Join(config.ConfigDir, name)
backup := util.AppendBackupSuffix(target)
if info, err := os.Stat(backup); err == nil {
input, err := os.ReadFile(backup)
if err == nil {
t := info.ModTime()
msg := fmt.Sprintf(buffer.BackupMsg, target, t.Format("Mon Jan _2 at 15:04, 2006"), backup)
choice := screen.TermPrompt(msg, []string{"r", "i", "a", "recover", "ignore", "abort"}, true)

if choice%3 == 0 {
// recover
err := os.WriteFile(target, input, util.FileMode)
if err != nil {
return err
}
return os.Remove(backup)
} else if choice%3 == 1 {
// delete
return os.Remove(backup)
} else if choice%3 == 2 {
// abort
return errors.New("Aborted")
}
}
}
return nil
}

func exit(rc int) {
for _, b := range buffer.OpenBuffers {
if !b.Modified() {
b.Fini()
}
}

if screen.Screen != nil {
screen.Screen.Fini()
}

os.Exit(rc)
}

func main() {
defer func() {
if util.Stdout.Len() > 0 {
fmt.Fprint(os.Stdout, util.Stdout.String())
}
os.Exit(0)
exit(0)
}()

var err error
Expand Down Expand Up @@ -256,6 +299,12 @@ func main() {
config.InitRuntimeFiles(true)
config.InitPlugins()

err = checkBackup("settings.json")
if err != nil {
screen.TermMessage(err)
exit(1)
}

err = config.ReadSettings()
if err != nil {
screen.TermMessage(err)
Expand Down Expand Up @@ -288,7 +337,7 @@ func main() {
if err != nil {
fmt.Println(err)
fmt.Println("Fatal: Micro could not initialize a Screen.")
os.Exit(1)
exit(1)
}
m := clipboard.SetMethod(config.GetGlobalOption("clipboard").(string))
clipErr := clipboard.Initialize(m)
Expand All @@ -307,7 +356,7 @@ func main() {
for _, b := range buffer.OpenBuffers {
b.Backup()
}
os.Exit(1)
exit(1)
}
}()

Expand All @@ -316,6 +365,12 @@ func main() {
screen.TermMessage(err)
}

err = checkBackup("bindings.json")
if err != nil {
screen.TermMessage(err)
exit(1)
}

action.InitBindings()
action.InitCommands()

Expand Down Expand Up @@ -434,41 +489,20 @@ func DoEvent() {
}
case f := <-timerChan:
f()
case b := <-buffer.BackupCompleteChan:
b.RequestedBackup = false
case <-sighup:
for _, b := range buffer.OpenBuffers {
if !b.Modified() {
b.Fini()
}
}
os.Exit(0)
exit(0)
case <-util.Sigterm:
for _, b := range buffer.OpenBuffers {
if !b.Modified() {
b.Fini()
}
}

if screen.Screen != nil {
screen.Screen.Fini()
}
os.Exit(0)
exit(0)
}

if e, ok := event.(*tcell.EventError); ok {
log.Println("tcell event error: ", e.Error())

if e.Err() == io.EOF {
// shutdown due to terminal closing/becoming inaccessible
for _, b := range buffer.OpenBuffers {
if !b.Modified() {
b.Fini()
}
}

if screen.Screen != nil {
screen.Screen.Fini()
}
os.Exit(0)
exit(0)
}
return
}
Expand Down
5 changes: 3 additions & 2 deletions internal/action/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -991,6 +991,9 @@ func (h *BufPane) SaveAsCB(action string, callback func()) bool {
h.completeAction(action)
return
}
} else {
InfoBar.Error(err)
return
}
} else {
InfoBar.YNPrompt(
Expand Down Expand Up @@ -1027,7 +1030,6 @@ func (h *BufPane) saveBufToFile(filename string, action string, callback func())
if err != nil {
InfoBar.Error(err)
} else {
h.Buf.Path = filename
JoeKar marked this conversation as resolved.
Show resolved Hide resolved
h.Buf.SetName(filename)
InfoBar.Message("Saved " + filename)
if callback != nil {
Expand All @@ -1053,7 +1055,6 @@ func (h *BufPane) saveBufToFile(filename string, action string, callback func())
InfoBar.Error(err)
}
} else {
h.Buf.Path = filename
h.Buf.SetName(filename)
InfoBar.Message("Saved " + filename)
if callback != nil {
Expand Down
23 changes: 15 additions & 8 deletions internal/action/bindings.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"io/fs"
"os"
"path/filepath"
"regexp"
Expand All @@ -14,6 +14,7 @@ import (
"github.com/zyedidia/json5"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell/v2"
)

Expand All @@ -23,9 +24,13 @@ var Binder = map[string]func(e Event, action string){
"terminal": TermMapEvent,
}

func writeFile(name string, txt []byte) error {
return util.SafeWrite(name, txt, false)
}

func createBindingsIfNotExist(fname string) {
if _, e := os.Stat(fname); os.IsNotExist(e) {
ioutil.WriteFile(fname, []byte("{}"), 0644)
if _, e := os.Stat(fname); errors.Is(e, fs.ErrNotExist) {
writeFile(fname, []byte("{}"))
}
}

Expand All @@ -37,7 +42,7 @@ func InitBindings() {
createBindingsIfNotExist(filename)

if _, e := os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
input, err := os.ReadFile(filename)
if err != nil {
screen.TermMessage("Error reading bindings.json file: " + err.Error())
return
Expand Down Expand Up @@ -265,7 +270,7 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) {
filename := filepath.Join(config.ConfigDir, "bindings.json")
createBindingsIfNotExist(filename)
if _, e = os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
input, err := os.ReadFile(filename)
if err != nil {
return false, errors.New("Error reading bindings.json file: " + err.Error())
}
Expand Down Expand Up @@ -304,7 +309,8 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) {
BindKey(k, v, Binder["buffer"])

txt, _ := json.MarshalIndent(parsed, "", " ")
return true, ioutil.WriteFile(filename, append(txt, '\n'), 0644)
txt = append(txt, '\n')
return true, writeFile(filename, txt)
}
return false, e
}
Expand All @@ -317,7 +323,7 @@ func UnbindKey(k string) error {
filename := filepath.Join(config.ConfigDir, "bindings.json")
createBindingsIfNotExist(filename)
if _, e = os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
input, err := os.ReadFile(filename)
if err != nil {
return errors.New("Error reading bindings.json file: " + err.Error())
}
Expand Down Expand Up @@ -354,7 +360,8 @@ func UnbindKey(k string) error {
}

txt, _ := json.MarshalIndent(parsed, "", " ")
return ioutil.WriteFile(filename, append(txt, '\n'), 0644)
txt = append(txt, '\n')
return writeFile(filename, txt)
}
return e
}
Expand Down
Loading