forked from TrueCloudLab/frostfs-node
[#1600] fstree: Handle incomplete writes
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
This commit is contained in:
parent
eff95bd632
commit
05fd999162
2 changed files with 63 additions and 4 deletions
|
@ -69,10 +69,13 @@ func (w *linuxWriter) writeFile(p string, data []byte) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
written := 0
|
||||||
tmpPath := "/proc/self/fd/" + strconv.FormatUint(uint64(fd), 10)
|
tmpPath := "/proc/self/fd/" + strconv.FormatUint(uint64(fd), 10)
|
||||||
n, err := unix.Write(fd, data)
|
n, err := unix.Write(fd, data)
|
||||||
if err == nil {
|
for err == nil {
|
||||||
if n == len(data) {
|
written += n
|
||||||
|
|
||||||
|
if written == len(data) {
|
||||||
err = unix.Linkat(unix.AT_FDCWD, tmpPath, unix.AT_FDCWD, p, unix.AT_SYMLINK_FOLLOW)
|
err = unix.Linkat(unix.AT_FDCWD, tmpPath, unix.AT_FDCWD, p, unix.AT_SYMLINK_FOLLOW)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
w.fileCounter.Inc(uint64(len(data)))
|
w.fileCounter.Inc(uint64(len(data)))
|
||||||
|
@ -80,9 +83,23 @@ func (w *linuxWriter) writeFile(p string, data []byte) error {
|
||||||
if errors.Is(err, unix.EEXIST) {
|
if errors.Is(err, unix.EEXIST) {
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
} else {
|
break
|
||||||
err = errors.New("incomplete write")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// From man 2 write:
|
||||||
|
// https://www.man7.org/linux/man-pages/man2/write.2.html
|
||||||
|
//
|
||||||
|
// Note that a successful write() may transfer fewer than count
|
||||||
|
// bytes. Such partial writes can occur for various reasons; for
|
||||||
|
// example, because there was insufficient space on the disk device
|
||||||
|
// to write all of the requested bytes, or because a blocked write()
|
||||||
|
// to a socket, pipe, or similar was interrupted by a signal handler
|
||||||
|
// after it had transferred some, but before it had transferred all
|
||||||
|
// of the requested bytes. In the event of a partial write, the
|
||||||
|
// caller can make another write() call to transfer the remaining
|
||||||
|
// bytes. The subsequent call will either transfer further bytes or
|
||||||
|
// may result in an error (e.g., if the disk is now full).
|
||||||
|
n, err = unix.Write(fd, data[written:])
|
||||||
}
|
}
|
||||||
errClose := unix.Close(fd)
|
errClose := unix.Close(fd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
//go:build linux && integration
|
||||||
|
|
||||||
|
package fstree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/common"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard/mode"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestENOSPC(t *testing.T) {
|
||||||
|
dir, err := os.MkdirTemp(t.TempDir(), "ramdisk")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
f, err := os.CreateTemp(t.TempDir(), "ramdisk_*")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = unix.Mount(f.Name(), dir, "tmpfs", 0, "size=1M")
|
||||||
|
if errors.Is(err, unix.EPERM) {
|
||||||
|
t.Skipf("skip size tests: no permission to mount: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer func() {
|
||||||
|
require.NoError(t, unix.Unmount(dir, 0))
|
||||||
|
}()
|
||||||
|
|
||||||
|
fst := New(WithPath(dir), WithDepth(1))
|
||||||
|
require.NoError(t, fst.Open(mode.ComponentReadWrite))
|
||||||
|
require.NoError(t, fst.Init())
|
||||||
|
|
||||||
|
_, err = fst.Put(context.Background(), common.PutPrm{
|
||||||
|
RawData: make([]byte, 10<<20),
|
||||||
|
})
|
||||||
|
require.ErrorIs(t, err, common.ErrNoSpace)
|
||||||
|
}
|
Loading…
Reference in a new issue