diff options
Diffstat (limited to 'libgo/go/cmd/go/internal/lockedfile/lockedfile.go')
-rw-r--r-- | libgo/go/cmd/go/internal/lockedfile/lockedfile.go | 65 |
1 files changed, 65 insertions, 0 deletions
diff --git a/libgo/go/cmd/go/internal/lockedfile/lockedfile.go b/libgo/go/cmd/go/internal/lockedfile/lockedfile.go index bb184b1085e..59b2dba44cd 100644 --- a/libgo/go/cmd/go/internal/lockedfile/lockedfile.go +++ b/libgo/go/cmd/go/internal/lockedfile/lockedfile.go @@ -120,3 +120,68 @@ func Write(name string, content io.Reader, perm os.FileMode) (err error) { } return err } + +// Transform invokes t with the result of reading the named file, with its lock +// still held. +// +// If t returns a nil error, Transform then writes the returned contents back to +// the file, making a best effort to preserve existing contents on error. +// +// t must not modify the slice passed to it. +func Transform(name string, t func([]byte) ([]byte, error)) (err error) { + f, err := Edit(name) + if err != nil { + return err + } + defer f.Close() + + old, err := ioutil.ReadAll(f) + if err != nil { + return err + } + + new, err := t(old) + if err != nil { + return err + } + + if len(new) > len(old) { + // The overall file size is increasing, so write the tail first: if we're + // about to run out of space on the disk, we would rather detect that + // failure before we have overwritten the original contents. + if _, err := f.WriteAt(new[len(old):], int64(len(old))); err != nil { + // Make a best effort to remove the incomplete tail. + f.Truncate(int64(len(old))) + return err + } + } + + // We're about to overwrite the old contents. In case of failure, make a best + // effort to roll back before we close the file. + defer func() { + if err != nil { + if _, err := f.WriteAt(old, 0); err == nil { + f.Truncate(int64(len(old))) + } + } + }() + + if len(new) >= len(old) { + if _, err := f.WriteAt(new[:len(old)], 0); err != nil { + return err + } + } else { + if _, err := f.WriteAt(new, 0); err != nil { + return err + } + // The overall file size is decreasing, so shrink the file to its final size + // after writing. We do this after writing (instead of before) so that if + // the write fails, enough filesystem space will likely still be reserved + // to contain the previous contents. + if err := f.Truncate(int64(len(new))); err != nil { + return err + } + } + + return nil +} |