small refactor*
This commit is contained in:
parent
b6b541e050
commit
24a4d30275
232 changed files with 2164 additions and 1906 deletions
server/pkg/cowutils
14
server/pkg/cowutils/cowutils.go
Normal file
14
server/pkg/cowutils/cowutils.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package cowutils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// ErrNotSupported is returned by Always() if the operation is not
|
||||
// supported on the current operating system. Auto() will never return this
|
||||
// error.
|
||||
var (
|
||||
ErrNotSupported = errors.New("cow is not supported on this OS")
|
||||
ErrFailed = errors.New("cow is not supported on this OS or file")
|
||||
ErrTooSmall = errors.New("file is too smaller then filesystem block size")
|
||||
)
|
88
server/pkg/cowutils/dedupe.go
Normal file
88
server/pkg/cowutils/dedupe.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
package cowutils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func DedupeFiles(ctx context.Context, paths []string) (deduped uint64, err error) {
|
||||
srcF, err := os.Open(paths[0])
|
||||
if err != nil {
|
||||
return deduped, err
|
||||
}
|
||||
defer srcF.Close()
|
||||
srcStat, err := srcF.Stat()
|
||||
if err != nil {
|
||||
return deduped, err
|
||||
}
|
||||
|
||||
srcFd := int(srcF.Fd())
|
||||
srcSize := srcStat.Size()
|
||||
|
||||
fsStat := unix.Statfs_t{}
|
||||
err = unix.Fstatfs(srcFd, &fsStat)
|
||||
if err != nil {
|
||||
return deduped, err
|
||||
}
|
||||
|
||||
if int64(fsStat.Bsize) > srcSize { // for btrfs it means file residing in metadata and can't be deduplicated
|
||||
return deduped, nil
|
||||
}
|
||||
|
||||
blockSize := uint64((srcSize % int64(fsStat.Bsize)) * int64(fsStat.Bsize))
|
||||
|
||||
fdr := unix.FileDedupeRange{
|
||||
Src_offset: 0,
|
||||
Src_length: blockSize,
|
||||
Info: []unix.FileDedupeRangeInfo{},
|
||||
}
|
||||
|
||||
for _, dst := range paths[1:] {
|
||||
if ctx.Err() != nil {
|
||||
return deduped, ctx.Err()
|
||||
}
|
||||
|
||||
destF, err := os.OpenFile(dst, os.O_RDWR, os.ModePerm)
|
||||
if err != nil {
|
||||
return deduped, err
|
||||
}
|
||||
|
||||
// defer in cycle is intended, file must be closed only at the end of the function,
|
||||
// and, most importantly, this keeps GC from closing descriptor while dudupe in progress
|
||||
defer destF.Close()
|
||||
|
||||
fdr.Info = append(fdr.Info, unix.FileDedupeRangeInfo{
|
||||
Dest_fd: int64(destF.Fd()),
|
||||
Dest_offset: 0,
|
||||
})
|
||||
}
|
||||
|
||||
if len(fdr.Info) == 0 {
|
||||
return deduped, nil
|
||||
}
|
||||
|
||||
if ctx.Err() != nil {
|
||||
return deduped, ctx.Err()
|
||||
}
|
||||
|
||||
fdr.Src_offset = 0
|
||||
for i := range fdr.Info {
|
||||
fdr.Info[i].Dest_offset = 0
|
||||
}
|
||||
|
||||
err = unix.IoctlFileDedupeRange(srcFd, &fdr)
|
||||
if err != nil {
|
||||
return deduped, err
|
||||
}
|
||||
|
||||
for i := range fdr.Info {
|
||||
deduped += fdr.Info[i].Bytes_deduped
|
||||
|
||||
fdr.Info[i].Status = 0
|
||||
fdr.Info[i].Bytes_deduped = 0
|
||||
}
|
||||
|
||||
return deduped, nil
|
||||
}
|
54
server/pkg/cowutils/reflink.go
Normal file
54
server/pkg/cowutils/reflink.go
Normal file
|
@ -0,0 +1,54 @@
|
|||
package cowutils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Reflink performs the reflink operation on the passed files, replacing
|
||||
// dst's contents with src. If fallback is true and reflink fails,
|
||||
// copy_file_range will be used first, and if that fails too io.Copy will
|
||||
// be used to copy the data.
|
||||
func Reflink(ctx context.Context, dst, src *os.File, fallback bool) error {
|
||||
err := reflink(dst, src)
|
||||
if (err != nil) && fallback {
|
||||
// reflink failed, but we can fallback, but first we need to know the file's size
|
||||
var st fs.FileInfo
|
||||
st, err = src.Stat()
|
||||
if err != nil {
|
||||
// couldn't stat source, this can't be helped
|
||||
return fmt.Errorf("failed to stat source: %w", err)
|
||||
}
|
||||
_, err = copyFileRange(dst, src, 0, 0, st.Size())
|
||||
if err != nil {
|
||||
// copyFileRange failed too, switch to simple io copy
|
||||
reader := io.NewSectionReader(src, 0, st.Size())
|
||||
writer := §ionWriter{w: dst}
|
||||
_ = dst.Truncate(0) // assuming any error in trucate will result in copy error
|
||||
_, err = io.Copy(writer, reader)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ReflinkRange performs a range reflink operation on the passed files, replacing
|
||||
// part of dst's contents with data from src. If fallback is true and reflink
|
||||
// fails, copy_file_range will be used first, and if that fails too io.CopyN
|
||||
// will be used to copy the data.
|
||||
func ReflinkRange(ctx context.Context, dst, src *os.File, dstOffset, srcOffset, n int64, fallback bool) error {
|
||||
err := reflinkRange(dst, src, dstOffset, srcOffset, n)
|
||||
if (err != nil) && fallback {
|
||||
_, err = copyFileRange(dst, src, dstOffset, srcOffset, n)
|
||||
}
|
||||
|
||||
if (err != nil) && fallback {
|
||||
// seek both src & dst
|
||||
reader := io.NewSectionReader(src, srcOffset, n)
|
||||
writer := §ionWriter{w: dst, base: dstOffset}
|
||||
_, err = io.CopyN(writer, reader, n)
|
||||
}
|
||||
return err
|
||||
}
|
53
server/pkg/cowutils/reflink_unix.go
Normal file
53
server/pkg/cowutils/reflink_unix.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
//!build +unix
|
||||
|
||||
package cowutils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// reflink performs the actual reflink action without worrying about fallback
|
||||
func reflink(dst, src *os.File) error {
|
||||
srcFd := int(src.Fd())
|
||||
dstFd := int(dst.Fd())
|
||||
|
||||
err := unix.IoctlFileClone(dstFd, srcFd)
|
||||
|
||||
if err != nil && errors.Is(err, unix.ENOTSUP) {
|
||||
return ErrNotSupported
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func reflinkRange(dst, src *os.File, dstOffset, srcOffset, n int64) error {
|
||||
srcFd := int(src.Fd())
|
||||
dstFd := int(dst.Fd())
|
||||
|
||||
req := &unix.FileCloneRange{
|
||||
Src_fd: int64(srcFd),
|
||||
Src_offset: uint64(srcOffset),
|
||||
Src_length: uint64(n),
|
||||
Dest_offset: uint64(dstOffset),
|
||||
}
|
||||
|
||||
err := unix.IoctlFileCloneRange(dstFd, req)
|
||||
if err != nil && errors.Is(err, unix.ENOTSUP) {
|
||||
return ErrNotSupported
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func copyFileRange(dst, src *os.File, dstOffset, srcOffset, n int64) (int64, error) {
|
||||
srcFd := int(src.Fd())
|
||||
dstFd := int(dst.Fd())
|
||||
|
||||
resN, err := unix.CopyFileRange(srcFd, &srcOffset, dstFd, &dstOffset, int(n), 0)
|
||||
|
||||
return int64(resN), err
|
||||
|
||||
}
|
39
server/pkg/cowutils/writer.go
Normal file
39
server/pkg/cowutils/writer.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package cowutils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// sectionWriter is a helper used when we need to fallback into copying data manually
|
||||
type sectionWriter struct {
|
||||
w io.WriterAt // target file
|
||||
base int64 // base position in file
|
||||
off int64 // current relative offset
|
||||
}
|
||||
|
||||
// Write writes & updates offset
|
||||
func (s *sectionWriter) Write(p []byte) (int, error) {
|
||||
n, err := s.w.WriteAt(p, s.base+s.off)
|
||||
s.off += int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (s *sectionWriter) Seek(offset int64, whence int) (int64, error) {
|
||||
switch whence {
|
||||
case io.SeekStart:
|
||||
// nothing needed
|
||||
case io.SeekCurrent:
|
||||
offset += s.off
|
||||
case io.SeekEnd:
|
||||
// we don't support io.SeekEnd
|
||||
fallthrough
|
||||
default:
|
||||
return s.off, errors.New("Seek: invalid whence")
|
||||
}
|
||||
if offset < 0 {
|
||||
return s.off, errors.New("Seek: invalid offset")
|
||||
}
|
||||
s.off = offset
|
||||
return offset, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue