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

proposal: provide ioutil.SafeWriteFile writing to a tmp file before overwriting an existing file #17869

Closed
griesemer opened this issue Nov 9, 2016 · 21 comments

Comments

@griesemer
Copy link
Contributor

If ioutil.WriteFile fails for some reason during writing/closing, it may destroy an existing file with the same name before reporting an error.

It's not hard to handle this on a case-by-case basis, but I suspect this is a common scenario for which to protect against and perhaps should be supported by ioutil.

See #8984 for a use case.

Implementation:

// SafeWriteFile is a drop-in replacement for ioutil.WriteFile;
// but SafeWriteFile writes data to a temporary file first and
// only upon success renames that file to filename.
func SafeWriteFile(filename string, data []byte, perm os.FileMode) error {
	// open temp file
	f, err := ioutil.TempFile(filepath.Dir(filename), "tmp")
	if err != nil {
		return err
	}
	err = f.Chmod(perm)
	if err != nil {
		return err
	}
	tmpname := f.Name()

	// write data to temp file
	n, err := f.Write(data)
	if err == nil && n < len(data) {
		err = io.ErrShortWrite
	}
	if err1 := f.Close(); err == nil {
		err = err1
	}
	if err != nil {
		return err
	}

	return os.Rename(tmpname, filename)
}

Better func name welcome.

@griesemer griesemer added this to the Proposal milestone Nov 9, 2016
@bradfitz
Copy link
Contributor

@mvdan
Copy link
Member

mvdan commented Nov 10, 2016

This seems like a good idea. I would prefer it to have a WriteFile prefix, but WriteFileSafe is confusing.

@bradfitz
Copy link
Contributor

Using an adverb would clarify what part is safe (the writing or the file): WriteFileSafely.

But maybe better to just replace the verb: ReplaceFile or something.

@minux
Copy link
Member

minux commented Nov 10, 2016 via email

@mattn
Copy link
Member

mattn commented Nov 10, 2016

I think the temporary file prefer on same disk-partition, same filesytem. How about to put the temporary file into same directory? ex: filepath.Join(filepath.Dir(filename), filepath.Base(filename)) + "~~~".

@mattn
Copy link
Member

mattn commented Nov 10, 2016

Sorry, already mentioned by grieseme.

@0xmohit
Copy link
Contributor

0xmohit commented Nov 10, 2016

In addition to what @minux mentioned, the proposed function should also preserve owner and group information.

@minux
Copy link
Member

minux commented Nov 10, 2016 via email

@griesemer
Copy link
Contributor Author

@minux Yes, that's the other approach I was thinking of. It will make updates much slower if there's a lot of files to change. What I have now preserves permissions, but that is only half the story.

@gopherbot
Copy link
Contributor

CL https://golang.org/cl/33098 mentions this issue.

gopherbot pushed a commit that referenced this issue Nov 10, 2016
This reverts the changes from https://golang.org/cl/33018: Instead
of writing the result of gofmt to a tmp file and then rename that
to the original (which doesn't preserve the original file's perm
bits, uid, gid, and possibly other properties because it is hard
to do in a platform-independent way - see #17869), use the original
code that simply overwrites the processed file if gofmt was able to
create a backup first. Upon success, the backup is removed, otherwise
it remains.

Fixes #17873.
For #8984.

Change-Id: Ifcf2bf1f84f730e6060f3517d63b45eb16215ae1
Reviewed-on: https://go-review.googlesource.com/33098
Run-TryBot: Robert Griesemer <gri@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
@slrz
Copy link

slrz commented Nov 11, 2016

I wonder if the "Safe" name prefix will raise crash safety expectations that won't be fulfilled by the function as proposed. Brad's ReplaceFile nicely avoids this connotation.

@griesemer
Copy link
Contributor Author

@slrz Yes, as I said in the original comment, we need a better name. Here's a few more ideas:
WriteFileAtomically
WriteFileTransaction
WriteFileAndReplace (or ReplaceFile per @bradfitz)

@0xmohit
Copy link
Contributor

0xmohit commented Nov 11, 2016

Maybe AtomicWrite or AtomicWriteFile.

@minux
Copy link
Member

minux commented Nov 11, 2016 via email

@dsnet
Copy link
Member

dsnet commented Nov 11, 2016

It seems that we're heading into the area of bikeshedding, but any name with the words 'Safe' or 'Atomic' in them seems to imply to me heavier guarantees of data persistence than the current proposed implementation gives. I would expect f.Sync() to be called in it's implementation, but that incurs serious performance costs.

@danp
Copy link
Contributor

danp commented Nov 12, 2016

Probably relevant commentary around atomic writing in #8868 and https://go-review.googlesource.com/c/1591/.

@kardianos
Copy link
Contributor

I haven't seen this mentioned before, but I've used: https://godoc.org/github.com/dchest/safefile before.

@rsc rsc changed the title Proposal: provide ioutil.SafeWriteFile writing to a tmp file before overwriting an existing file proposal: provide ioutil.SafeWriteFile writing to a tmp file before overwriting an existing file Nov 14, 2016
@rsc
Copy link
Contributor

rsc commented Nov 14, 2016

This seems very hard and maybe beyond the scope of the library.

@rsc
Copy link
Contributor

rsc commented Dec 12, 2016

We don't see how to do a 100% safe implementation of this on most systems. Gofmt changed to copy the old file to a temp file, then overwrite the old file, and then if the overwrite fails, attempt to rename the tempfile into the old file's place, which is actually different from the initial proposal.

You can have the one that clears permission bits / ACLs on success.
You can have the one that clears permission bits / ACLs on failure.
There doesn't seem to be one that is 100% "overwrite the file (and do nothing else) or else do nothing else." You have to pick which compromise you want that means "safe" in this case. Your choice may not be another person's choice.

If the community converges on a particular semantics that is the "right" one, we can certainly revisit this. But right now we don't know what this should do, especially well enough to put in the standard library.

Objections to declining this proposal?

@mvdan
Copy link
Member

mvdan commented Dec 19, 2016

You have to pick which compromise you want that means "safe" in this case. Your choice may not be another person's choice.

Would it be sensible for the standard library to include one or many of these functions that compromise in one way or another? From what I understand, you'd want the "right" one (as in not many at once) or at least for most people to agree on which is the better one.

@dsnet
Copy link
Member

dsnet commented Dec 19, 2016

I would rather see a third party package provide a set of functions that emphasis different design priorities rather than seeing the standard library provide a hodgepodge of different implementations.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests