diff --git a/pkg/local_object_storage/blobstor/fstree/fstree_write_linux.go b/pkg/local_object_storage/blobstor/fstree/fstree_write_linux.go index c6265402..49cbda34 100644 --- a/pkg/local_object_storage/blobstor/fstree/fstree_write_linux.go +++ b/pkg/local_object_storage/blobstor/fstree/fstree_write_linux.go @@ -69,10 +69,13 @@ func (w *linuxWriter) writeFile(p string, data []byte) error { if err != nil { return err } + written := 0 tmpPath := "/proc/self/fd/" + strconv.FormatUint(uint64(fd), 10) n, err := unix.Write(fd, data) - if err == nil { - if n == len(data) { + for err == nil { + written += n + + if written == len(data) { err = unix.Linkat(unix.AT_FDCWD, tmpPath, unix.AT_FDCWD, p, unix.AT_SYMLINK_FOLLOW) if err == nil { 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) { err = nil } - } else { - err = errors.New("incomplete write") + break } + + // 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) if err != nil { diff --git a/pkg/local_object_storage/blobstor/fstree/fstree_write_linux_test.go b/pkg/local_object_storage/blobstor/fstree/fstree_write_linux_test.go new file mode 100644 index 00000000..7fae2e69 --- /dev/null +++ b/pkg/local_object_storage/blobstor/fstree/fstree_write_linux_test.go @@ -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) +}