ioutil.TempFile和umask

huangapple go评论93阅读模式
英文:

ioutil.TempFile and umask

问题

在我的Go应用程序中,我希望将内容写入一个临时文件,然后在所有操作完成后将其重命名为最终文件,以避免在应用程序崩溃时留下部分写入的内容。

目前我使用的是ioutil.TempFile,但问题是它创建的文件权限是0600,而不是0666。因此,根据典型的umask值,得到的是0600权限,而不是预期的0644或0660。如果目标文件已经存在,这不是问题,因为我可以将临时文件的权限设置为与现有文件相同,但如果文件不存在,我需要找到当前的umask值。

我想我可以复制ioutil.TempFile的实现,在os.OpenFile中传递0666,但这听起来不太好。所以问题是,有没有更好的方法?

英文:

In my Go application instead of writing to a file directly I would like to write to a temporary that is renamed into the final file when everything is done. This is to avoid leaving partially written content in the file if the application crashes.

Currently I use ioutil.TempFile, but the issue is that it creates the file with the 0600 permission, not 0666. Thus with typical umask values one gets the 0600 permission, not expected 0644 or 0660. This is not a problem is the destination file already exist as I can fix the permission on the temporary to much the existing ones, but if the file does not exist, then I need somehow to deduce the current umask.

I suppose I can just duplicate ioutil.TempFile implementation to pass 0666 into os.OpenFile, but that does not sound nice. So the question is there a better way?

答案1

得分: 1

你可以使用ioutil.TempDir来获取临时文件应存储的文件夹,然后使用正确的权限自行创建文件。

英文:

You can use ioutil.TempDir to get the folder where temporary files should be stored an than create the file on your own with the right permissions.

答案2

得分: 1

我不太理解你的问题。

临时文件必须以尽可能严格的权限创建,因为创建它们的整个目的是为了为你的应用程序提供安全的临时存储数据的方式,这些数据太大而无法放入内存中(或者将生成的文件交给另一个进程)。在 POSIX 系统上(其中打开的文件被视为对其的活动引用),甚至习惯上在打开文件的同时立即删除该文件,以确保除了创建它的进程写入数据之外,没有其他方式修改其数据。

所以在我看来,你正在尝试使用错误的解决方案来解决你的问题。

所以在像你这样的情况下,我会这样做:

  1. 创建一个与旧文件同名但带有“.temp”后缀的文件。
  2. 在那里写入数据。
  3. 关闭文件,将其重命名为旧文件。

如果你觉得使用固定后缀很糟糕,你可以从ioutil.TempFile()中“借用”选择唯一且不冲突的文件名的实现。但在我看来,这可能过于工程化了。

英文:

I don't quite grok your problem.

Temporary files must be created with as tight permissions as possible because the whole idea of having them is to provide your application with secure means of temporary storing data which is too big to fit in memory (or to hand the generated file over to another process). (Note that on POSIX systems, where an opened file counts as a live reference to it, it's even customary to immediately remove the file while having it open so that there's no way to modify its data other than writing it from the process which created it.)

So in my opinion you're trying to use a wrong solution to your problem.

So what I do in a case like yours is:

  1. Create a file with the same name as old one but with the ".temp" suffix appended.
  2. Write data there.
  3. Close, rename it over the old one.

If you feel like using a fixed suffix is lame, you can "steal" the implementation of picking a unique non-conflicting file name from ioutil.TempFile(). But IMO this would be overengeneering.

答案3

得分: 0

完全符合问题要求的代码如下:

package main

import (
	"bytes"
	"fmt"
	"io"
	"io/fs"
	"os"
	"path/filepath"
	"syscall"
)

func getCurrentUmask() int {
	// 获取当前的 umask
	currentUmask := syscall.Umask(0)
	// 恢复为先前的值
	syscall.Umask(currentUmask)
	// 返回值
	return currentUmask
}

func atomicWriteFile(filename string, data []byte, perm os.FileMode) error {
	reader := bytes.NewBuffer(data)
	dataSize := int64(len(data))

	tmpFile, err := os.CreateTemp(filepath.Dir(filename), ".tmp-"+filepath.Base(filename))
	if err != nil {
		return err
	}

	err = os.Chmod(tmpFile.Name(), perm)
	if err != nil {
		tmpFile.Close()
		return err
	}

	n, err := io.Copy(tmpFile, reader)
	if err == nil && n < dataSize {
		tmpFile.Close()
		return io.ErrShortWrite
	}

	if err != nil {
		tmpFile.Close()
		return err
	}

	if err := tmpFile.Sync(); err != nil {
		tmpFile.Close()
		return err
	}

	if err := tmpFile.Close(); err != nil {
		return err
	}

	return os.Rename(tmpFile.Name(), filename)
}

func main() {
	// 你通常会为文件设置的权限
	targetPermissionForFile := 0o666

	// 通常的文件写入方式
	os.WriteFile("using_os.txt", []byte("barbar"), fs.FileMode(targetPermissionForFile))

	// 获取当前的 umask
	mask := getCurrentUmask()

	// 使用原子写入方法进行写入
	atomicWriteFile("using_atomic.txt", []byte("foofoo"), fs.FileMode((^mask)&targetPermissionForFile))

	// 比较结果的权限
	info, _ := os.Stat("using_os.txt")
	fmt.Printf("使用 os.WriteFile 写入的文件权限为:%#o\n", info.Mode())

	info, _ = os.Stat("using_atomic.txt")
	fmt.Printf("使用 atomicWrite 写入的文件权限为:%#o\n", info.Mode())
}

希望对你有帮助!

英文:

Matching exactly the requirements of the question, that should do:

package main

import (
	&quot;bytes&quot;
	&quot;fmt&quot;
	&quot;io&quot;
	&quot;io/fs&quot;
	&quot;os&quot;
	&quot;path/filepath&quot;
	&quot;syscall&quot;
)

func getCurrentUmask() int {
	// Get current umask
	currentUmask := syscall.Umask(0)
	// Restore it to previous value
	syscall.Umask(currentUmask)
	// Return the value
	return currentUmask
}

func atomicWriteFile(filename string, data []byte, perm os.FileMode) error {
	reader := bytes.NewBuffer(data)
	dataSize := int64(len(data))

	tmpFile, err := os.CreateTemp(filepath.Dir(filename), &quot;.tmp-&quot;+filepath.Base(filename))
	if err != nil {
		return err
	}

	err = os.Chmod(tmpFile.Name(), perm)
	if err != nil {
		tmpFile.Close()

		return err
	}

	n, err := io.Copy(tmpFile, reader)
	if err == nil &amp;&amp; n &lt; dataSize {
		tmpFile.Close()

		return io.ErrShortWrite
	}

	if err != nil {
		tmpFile.Close()

		return err
	}

	if err := tmpFile.Sync(); err != nil {
		tmpFile.Close()

		return err
	}

	if err := tmpFile.Close(); err != nil {
		return err
	}

	return os.Rename(tmpFile.Name(), filename)
}

func main() {
	// Whatever permission you would normally set on your file
	targetPermissionForFile := 0o666

	// How you would normally write a file
	os.WriteFile(&quot;using_os.txt&quot;, []byte(&quot;barbar&quot;), fs.FileMode(targetPermissionForFile))

	// Retrieving the current umask
	mask := getCurrentUmask()

	// Writing it with the atomic method
	atomicWriteFile(&quot;using_atomic.txt&quot;, []byte(&quot;foofoo&quot;), fs.FileMode((^mask)&amp;targetPermissionForFile))

	// Comparing resulting permissions
	info, _ := os.Stat(&quot;using_os.txt&quot;)
	fmt.Printf(&quot;File written using os.WriteFile has permissions: %#o\n&quot;, info.Mode())

	info, _ = os.Stat(&quot;using_atomic.txt&quot;)
	fmt.Printf(&quot;File written using atomicWrite has permissions: %#o\n&quot;, info.Mode())

}

huangapple
  • 本文由 发表于 2016年2月11日 18:41:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/35337046.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定