summaryrefslogtreecommitdiff
path: root/libgo/go/cmd/go/internal/lockedfile/lockedfile.go
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/cmd/go/internal/lockedfile/lockedfile.go')
-rw-r--r--libgo/go/cmd/go/internal/lockedfile/lockedfile.go65
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
+}