A tool that several users, or several runs, share has three problems with
its files. A reader can see a file written in part. Two runs can change a
shared file at once, and one change is lost. And a file that another user
can write can make the tool run their commands. fsutil solves the three
(decision 37).
| Problem | Use |
|---|---|
| A reader sees half a file | fsutil.WriteAtomic: a temporary file, a sync and a rename |
| Two runs change one file | fsutil.Update: the change under fsutil.Lock |
| Another user put the file there | fsutil.CheckTrusted and fsutil.EnsureDir, which refuse with fsutil.ErrUntrusted |
| A private file was made readable | fsutil.ExactMode |
Change a shared file#
- Give
Updatethe path, the mode and a function that changes the old content into the new. Updatetakes the lock beside the file, waits at mostfsutil.DefaultLockWait, calls the function, and writes the result whole.- When the wait passes, the error is a timeout and names the holder of the lock.
dir, _ := os.MkdirTemp("", "prog-")
defer func() { _ = os.RemoveAll(dir) }()
path := filepath.Join(dir, "runs")
count := func(current []byte) ([]byte, error) {
n := 0
if current != nil { // nil: there is no file yet
var err error
if n, err = strconv.Atoi(string(current)); err != nil {
return nil, err // nothing is written
}
}
return []byte(strconv.Itoa(n + 1)), nil
}
for range 3 {
if err := fsutil.Update(context.Background(), path, 0o644, count); err != nil {
fmt.Println(err)
return
}
}
data, _ := os.ReadFile(path)
fmt.Println(string(data)) 3fsutil/example_test.go. CI runs it and checks its output.Refuse what others wrote#
dir, _ := os.MkdirTemp("", "prog-")
defer func() { _ = os.RemoveAll(dir) }()
path := filepath.Join(dir, "config.toml")
_ = os.WriteFile(path, []byte("ssh = \"ssh\"\n"), 0o644)
fmt.Println(fsutil.CheckTrusted(path))
_ = os.Chmod(path, 0o666)
fmt.Println(errors.Is(fsutil.CheckTrusted(path), fsutil.ErrUntrusted))<nil>
truefsutil/example_test.go. CI runs it and checks its output.WARNING
Read configuration that names commands to run only after CheckTrusted
accepts it. A file that another user could write gives that user the
commands of your program.
The name and the mode of the lock file are an interface of the kit, so two releases of a program lock the same file.