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 29 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
29 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
2f02db5
save: Perform write process safe
JoeKar May 29, 2024
bf9c7c5
actions: Don't overwrite the buffers `Path`
JoeKar Oct 1, 2024
5bb742b
util: Provide `AppendBackupSuffix()` for further transformations
JoeKar Sep 3, 2024
0f9d715
backup: Perform write process safe
JoeKar May 31, 2024
f203dc9
util: Provide `SafeWrite()` to generalize the internal file write pro…
JoeKar Aug 29, 2024
957bb66
serialize: Perform write process safe
JoeKar Jun 1, 2024
825570b
bindings: Perform write process safe
JoeKar Jun 1, 2024
eb23119
settings: Perform write process safe
JoeKar Jun 1, 2024
62d8458
save: Merge `overwrite()` into `overwriteFile()` and extract `writeFi…
JoeKar Oct 2, 2024
5e25d68
micro: Generalize exit behavior
JoeKar Sep 8, 2024
48f945c
micro: Provide recovery of `settings.json` & `bindings.json`
JoeKar Sep 8, 2024
ab1fb37
backup: Rearrange and extend `BackupMsg`
JoeKar Oct 30, 2024
8f25a29
buffer: Remove superfluous `backupTime`
JoeKar Oct 12, 2024
20b77bd
save+backup: Process the `save` & `backup` with a sequential channel
JoeKar Oct 1, 2024
6ec4771
backup: Clear the requested backup upon completion notification
JoeKar Oct 12, 2024
8053648
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
63 changes: 63 additions & 0 deletions internal/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io"
"io/fs"
"net/http"
"net/url"
"os"
Expand Down Expand Up @@ -616,3 +617,65 @@ func HttpRequest(method string, url string, headers []string) (resp *http.Respon
}
return client.Do(req)
}

// SafeWrite writes bytes to a file in a "safe" way, preventing loss of the
// contents of the file if it fails to write the new contents.
// This means that the file is not overwritten directly but by writing to a
// temporary file first.
JoeKar marked this conversation as resolved.
Show resolved Hide resolved
// If rename is true, write is performed atomically, by renaming the temporary
// file to the target file after the data is successfully written to the
// temporary file. This guarantees that the file will not remain in a corrupted
// state, but it also has limitations, e.g. the file should not be a symlink
// (otherwise SafeWrite silently replaces this symlink with a regular file),
// the file creation date in Linux is not preserved (since the file inode
// changes) etc. Use SafeWrite with rename=true for files that are only created
// and used by micro for its own needs and are not supposed to be used directly
// by the user.
// If rename is false, write is performed by overwriting the target file after
// the data is successfully written to the temporary file.
// This means that the target file may remain corrupted if overwrite fails,
// but in such case the temporary file is preserved as a backup so the file
// can be recovered later. So it is less convenient than atomic write but more
// universal. Use SafeWrite with rename=false for files that may be managed
// directly by the user, like settings.json and bindings.json.
func SafeWrite(path string, bytes []byte, rename bool) error {
if _, err := os.Stat(path); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return err
}
return os.WriteFile(path, bytes, FileMode)
}

// Try to open the file first to identify issues before the backup+write attempt
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, FileMode)
if err != nil {
return err
}
if err := f.Close(); err != nil {
return err
}

tmp := AppendBackupSuffix(path)
err = os.WriteFile(tmp, bytes, FileMode)
if err != nil {
os.Remove(tmp)
return err
}

if rename {
err = os.Rename(tmp, path)
} else {
err = os.WriteFile(path, bytes, FileMode)
}
if err != nil {
if rename {
os.Remove(tmp)
}
return err
}

if !rename {
os.Remove(tmp)
}
return nil
}