Repository navigation
test: ✅🐛 Source audits for renames and transports #1615
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -451,17 +451,42 @@ func writeRecord(dir string, rec installRecord) error { | |
| return writeAtomic(filepath.Join(dir, scriptRecord), append(b, '\n'), 0o600) | ||
| } | ||
|
|
||
| // writeAtomic writes b to path through a temp file whose name a slot ignores. | ||
| // writeAtomic writes b to path through a CreateTemp'd file whose dot-prefixed | ||
| // name a slot ignores, synced before the rename: this also writes the install | ||
| // record, and a crash that empties THAT file wedges both install and remove on | ||
| // a folder neither can make sense of. | ||
| func writeAtomic(path string, b []byte, mode os.FileMode) error { | ||
| tmp := filepath.Join(filepath.Dir(path), "."+filepath.Base(path)+".tmp") | ||
| if err := os.WriteFile(tmp, b, mode); err != nil { | ||
| f, err := os.CreateTemp(filepath.Dir(path), "."+filepath.Base(path)+".tmp*") | ||
| if err != nil { | ||
| return err | ||
| } | ||
| tmp := f.Name() | ||
| ok := false | ||
| defer func() { | ||
| if !ok { | ||
| _ = os.Remove(tmp) | ||
| } | ||
| }() | ||
| if _, err := f.Write(b); err != nil { | ||
| _ = f.Close() | ||
| return err | ||
| } | ||
| if err := f.Chmod(mode); err != nil { | ||
| _ = f.Close() | ||
| return err | ||
| } | ||
| if err := os.Chmod(tmp, mode); err != nil { | ||
| os.Remove(tmp) | ||
| if err := f.Sync(); err != nil { | ||
| _ = f.Close() | ||
| return err | ||
| } | ||
| return os.Rename(tmp, path) | ||
| if err := f.Close(); err != nil { | ||
| return err | ||
| } | ||
| if err := os.Rename(tmp, path); err != nil { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sync the directory after the rename. If power fails after an update returns, the rename can be lost even though the file was synced. The installed scripts can then disagree with the install record, causing Sync the parent directory after each rename. Also account for newly created directories. Based on learnings: sync the temp file before rename and the containing directory afterward.
|
||
| return err | ||
| } | ||
| ok = true | ||
| return nil | ||
| } | ||
|
|
||
| // removeFiles deletes what rec lists, the record, then dir when it is empty. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: The temp file is now
.<base>.tmp<random>, butTestReplaceConfig_VerifyFailureKeepsTheOriginal(cmd_config_migrate_pricing_test.go:307) still checksos.Stat(p + ".tmp"). That path can no longer exist, so the "temp file left behind" assertion can never fail. I checked by deleting thedefer os.Remove(tmpName)on line 235: the test still passes and the temp file stays in the directory. Could you make the test check the directory for."+filepath.Base(p)+".tmp*"(ascmd_experimental_test.godoes) so the cleanup this PR moved into thedeferis still tested?TestSaveUserConfig_LeavesNoTempFilealready had the same blind spot before this PR, so it may be worth fixing both together.