Test / test (push) Successful in 2m4s
Release / gates (push) Successful in 2m5s
Release / build (amd64, freebsd) (push) Successful in 1m27s
Release / build (amd64, linux) (push) Successful in 1m22s
Release / build (amd64, netbsd) (push) Successful in 1m19s
Release / build (amd64, openbsd) (push) Successful in 1m20s
Release / build (arm64, darwin) (push) Successful in 1m21s
Release / build (arm64, freebsd) (push) Successful in 1m26s
Release / build (arm64, linux) (push) Successful in 1m25s
Release / build (arm64, netbsd) (push) Successful in 1m31s
Release / build (arm64, openbsd) (push) Successful in 1m27s
Release / build (loong64, linux) (push) Successful in 1m37s
Release / build (riscv64, linux) (push) Successful in 1m21s
Release / release (push) Successful in 40s
Assisted-by: GLM 5.3 Flash
601 lines
16 KiB
Go
601 lines
16 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
// The handlers of the NFSv4.2 operations, RFC 7862.
|
|
|
|
package nfs4server
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"sourcedock.dev/petrbalvin/nfs/internal/nfs4"
|
|
"sourcedock.dev/petrbalvin/nfs/internal/nfsfs"
|
|
"sourcedock.dev/petrbalvin/nfs/internal/xdr"
|
|
)
|
|
|
|
// A seeker is the backend's hole seeking half: the answer offset, the
|
|
// eof flag that names the virtual hole at the end of every file, and
|
|
// ErrBeyondEOF for a request past the end, RFC 7862 section 15.11.
|
|
type seeker interface {
|
|
SeekHole(nfsfs.Handle, int64) (int64, bool, error)
|
|
SeekData(nfsfs.Handle, int64) (int64, bool, error)
|
|
}
|
|
|
|
// seeker resolves the backend's hole seeking half.
|
|
func (h *Handler) seeker() seeker {
|
|
s, _ := h.FS.(seeker)
|
|
return s
|
|
}
|
|
|
|
// allocator resolves the backend's space reservation half.
|
|
func (h *Handler) allocator() interface {
|
|
Allocate(nfsfs.Handle, int64, int64) error
|
|
Deallocate(nfsfs.Handle, int64, int64) error
|
|
} {
|
|
a, _ := h.FS.(interface {
|
|
Allocate(nfsfs.Handle, int64, int64) error
|
|
Deallocate(nfsfs.Handle, int64, int64) error
|
|
})
|
|
return a
|
|
}
|
|
|
|
// readStateid pulls one stateid off the wire.
|
|
func readStateid(d *xdr.Decoder) (nfs4.Stateid, error) {
|
|
var st nfs4.Stateid
|
|
raw, err := d.Raw(16)
|
|
if err != nil {
|
|
return st, err
|
|
}
|
|
copy(st[:], raw)
|
|
return st, nil
|
|
}
|
|
|
|
// checkOpStateid validates the stateid of a stateful data operation:
|
|
// it routes by the family mark to the store that minted it, the
|
|
// caller's own open of the file, the caller's own lock state on it, or
|
|
// the caller's own delegation of it, RFC 8881 section 10.3.
|
|
func (h *Handler) checkOpStateid(st nfs4.Stateid, fh nfsfs.Handle, clientid uint64) uint32 {
|
|
switch string(st[4:8]) {
|
|
case "LOCK":
|
|
if _, status := h.locks().byStateid(st, fh, clientid); status != nfs4.ErrOK {
|
|
return status
|
|
}
|
|
return nfs4.ErrOK
|
|
case "DELE":
|
|
return h.delegs().checkDataStateid(st, fh, clientid)
|
|
default:
|
|
_, status := h.openStates().checkStateid(st, fh, clientid)
|
|
return status
|
|
}
|
|
}
|
|
|
|
// seekOp serves SEEK: the next hole or data byte from the offset.
|
|
func (h *Handler) seekOp(d *xdr.Decoder, reg *fhreg) ([]byte, uint32, error) {
|
|
st, err := readStateid(d)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
offset, err := d.Uint64()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
what, err := d.Uint32()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
if what != nfs4.ContentData && what != nfs4.ContentHole {
|
|
return nil, nfs4.ErrInval, nil
|
|
}
|
|
if !reg.haveCur {
|
|
return nil, nfs4.ErrNoFileHandle, nil
|
|
}
|
|
if status := h.checkOpStateid(st, reg.cur, reg.clientID); status != nfs4.ErrOK {
|
|
return nil, status, nil
|
|
}
|
|
s := h.seeker()
|
|
if s == nil {
|
|
return nil, nfs4.ErrNotSupp, nil
|
|
}
|
|
seek := s.SeekData
|
|
if what == nfs4.ContentHole {
|
|
seek = s.SeekHole
|
|
}
|
|
if offset > 1<<62 {
|
|
return nil, nfs4.ErrNXIO, nil
|
|
}
|
|
found, eof, serr := seek(reg.cur, int64(offset))
|
|
if serr == nfsfs.ErrBeyondEOF {
|
|
return nil, nfs4.ErrNXIO, nil
|
|
}
|
|
if serr != nil {
|
|
return nil, mapErr(serr), nil
|
|
}
|
|
return nfs4.AppendSeekRes(nil, eof, uint64(found)), nfs4.ErrOK, nil
|
|
}
|
|
|
|
// rangeOp serves ALLOCATE and DEALLOCATE through the backend's space
|
|
// reservation half.
|
|
func (h *Handler) rangeOp(d *xdr.Decoder, reg *fhreg, deallocate bool) ([]byte, uint32, error) {
|
|
st, err := readStateid(d)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
offset, err := d.Uint64()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
length, err := d.Uint64()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
if !reg.haveCur {
|
|
return nil, nfs4.ErrNoFileHandle, nil
|
|
}
|
|
if status := h.checkOpStateid(st, reg.cur, reg.clientID); status != nfs4.ErrOK {
|
|
return nil, status, nil
|
|
}
|
|
a := h.allocator()
|
|
if a == nil {
|
|
return nil, nfs4.ErrNotSupp, nil
|
|
}
|
|
if length == 0 {
|
|
return nil, nfs4.ErrInval, nil
|
|
}
|
|
if deallocate {
|
|
err = a.Deallocate(reg.cur, int64(offset), int64(length))
|
|
} else {
|
|
err = a.Allocate(reg.cur, int64(offset), int64(length))
|
|
}
|
|
if err != nil {
|
|
return nil, mapErr(err), nil
|
|
}
|
|
return nil, nfs4.ErrOK, nil
|
|
}
|
|
|
|
// ioAdviseOp serves IO_ADVISE: the hints ride through, no state kept.
|
|
func (h *Handler) ioAdviseOp(d *xdr.Decoder, reg *fhreg) ([]byte, uint32, error) {
|
|
st, err := readStateid(d)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
if _, err = d.Uint64(); err != nil { // offset
|
|
return nil, 0, err
|
|
}
|
|
if _, err = d.Uint64(); err != nil { // count
|
|
return nil, 0, err
|
|
}
|
|
hints, err := nfs4.ReadBitmap(d)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
if !reg.haveCur {
|
|
return nil, nfs4.ErrNoFileHandle, nil
|
|
}
|
|
if status := h.checkOpStateid(st, reg.cur, reg.clientID); status != nfs4.ErrOK {
|
|
return nil, status, nil
|
|
}
|
|
return nfs4.AppendIoAdviseRes(nil, hints), nfs4.ErrOK, nil
|
|
}
|
|
|
|
// copyOp serves COPY between two files of this server: the saved handle
|
|
// is the source, the current one the destination, the copy runs
|
|
// synchronously in this compound.
|
|
func (h *Handler) copyOp(d *xdr.Decoder, reg *fhreg) ([]byte, uint32, error) {
|
|
src, err := readStateid(d)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
dst, err := readStateid(d)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
srcOff, err := d.Uint64()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
dstOff, err := d.Uint64()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
count, err := d.Uint64()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
consecutive, err := d.Bool()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
synchronous, err := d.Bool()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
sources, err := readNetlocList(d)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
_ = sources
|
|
if !reg.haveCur || reg.saved == nil {
|
|
return nil, nfs4.ErrNoFileHandle, nil
|
|
}
|
|
if status := h.checkOpStateid(src, reg.saved, reg.clientID); status != nfs4.ErrOK {
|
|
return nil, status, nil
|
|
}
|
|
if status := h.checkOpStateid(dst, reg.cur, reg.clientID); status != nfs4.ErrOK {
|
|
return nil, status, nil
|
|
}
|
|
if count > nfs4.DefaultLimits.MaxRead {
|
|
return nil, nfs4.ErrTooSmall, nil
|
|
}
|
|
// The kernel path moves the bytes without userspace touching them
|
|
// where the backend and the filesystem provide it; anywhere else the
|
|
// userspace loop carries the copy, the answer being the same.
|
|
var moved uint64
|
|
if cl, ok := h.FS.(nfsfs.RangeCloner); ok {
|
|
if err := cl.CopyRange(reg.saved, int64(srcOff), reg.cur, int64(dstOff), int64(count)); err == nil {
|
|
moved = count
|
|
}
|
|
}
|
|
if moved == 0 {
|
|
data, rerr := h.FS.Read(reg.saved, int64(srcOff), int(count))
|
|
if rerr != nil {
|
|
return nil, mapErr(rerr), nil
|
|
}
|
|
w := h.writer()
|
|
if w == nil {
|
|
return nil, nfs4.ErrROFS, nil
|
|
}
|
|
n, werr := w.Write(reg.cur, int64(dstOff), data)
|
|
if werr != nil {
|
|
return nil, mapErr(werr), nil
|
|
}
|
|
moved = uint64(n)
|
|
}
|
|
return nfs4.AppendCopyRes(nil, nfs4.Stateid{}, false, moved,
|
|
nfs4.NfsSyncFileSync, h.writeVerifier(), consecutive, synchronous), nfs4.ErrOK, nil
|
|
}
|
|
|
|
// readNetlocList decodes the netloc4 list the copy family carries.
|
|
func readNetlocList(d *xdr.Decoder) ([]nfs4.CopySourceServer, error) {
|
|
n, err := d.Uint32()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var out []nfs4.CopySourceServer
|
|
for range n {
|
|
var s nfs4.CopySourceServer
|
|
if s.Type, err = d.Uint32(); err != nil {
|
|
return nil, err
|
|
}
|
|
switch s.Type {
|
|
case 1, 2:
|
|
if s.Name, err = d.String(); err != nil {
|
|
return nil, err
|
|
}
|
|
case 3:
|
|
if s.Addr.Netid, err = d.String(); err != nil {
|
|
return nil, err
|
|
}
|
|
if s.Addr.Uaddr, err = d.String(); err != nil {
|
|
return nil, err
|
|
}
|
|
default:
|
|
return nil, errors.New("nfs4server: unknown netloc type")
|
|
}
|
|
out = append(out, s)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// copyNotifyOp serves COPY_NOTIFY: the source server grants the copy to
|
|
// the named destination.
|
|
func (h *Handler) copyNotifyOp(d *xdr.Decoder, reg *fhreg) ([]byte, uint32, error) {
|
|
st, err := readStateid(d)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
dstType, err := d.Uint32()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
var dst nfs4.CopySourceServer
|
|
dst.Type = dstType
|
|
switch dstType {
|
|
case 1, 2:
|
|
if dst.Name, err = d.String(); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
case 3:
|
|
if dst.Addr.Netid, err = d.String(); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
if dst.Addr.Uaddr, err = d.String(); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
default:
|
|
return nil, nfs4.ErrInval, nil
|
|
}
|
|
if !reg.haveCur {
|
|
return nil, nfs4.ErrNoFileHandle, nil
|
|
}
|
|
if status := h.checkOpStateid(st, reg.cur, reg.clientID); status != nfs4.ErrOK {
|
|
return nil, status, nil
|
|
}
|
|
setStateidSeq(&st, 1)
|
|
copy(st[4:], "CPYN")
|
|
return nfs4.AppendCopyNotifyRes(nil, int64(h.leasePeriod().Seconds()), st,
|
|
[]nfs4.CopySourceServer{{Type: 3, Addr: nfs4.NetAddr{Netid: "tcp", Uaddr: h.deviceAddr(nil)}}}), nfs4.ErrOK, nil
|
|
}
|
|
|
|
// offloadCancelOp serves OFFLOAD_CANCEL: nothing async is in flight in
|
|
// this build, so the stateid is checked and the call answered.
|
|
func (h *Handler) offloadCancelOp(d *xdr.Decoder, reg *fhreg) ([]byte, uint32, error) {
|
|
st, err := readStateid(d)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
if !reg.haveCur {
|
|
return nil, nfs4.ErrNoFileHandle, nil
|
|
}
|
|
if status := h.checkOpStateid(st, reg.cur, reg.clientID); status != nfs4.ErrOK {
|
|
return nil, status, nil
|
|
}
|
|
return nil, nfs4.ErrOK, nil
|
|
}
|
|
|
|
// offloadStatusOp serves OFFLOAD_STATUS: no asynchronous copy was ever
|
|
// requested, which the standard answers OFFLOAD_NO_REQS for.
|
|
func (h *Handler) offloadStatusOp(d *xdr.Decoder, reg *fhreg) ([]byte, uint32, error) {
|
|
if _, err := readStateid(d); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
if !reg.haveCur {
|
|
return nil, nfs4.ErrNoFileHandle, nil
|
|
}
|
|
return nil, nfs4.ErrOffloadNoReqs, nil
|
|
}
|
|
|
|
// cloneOp serves CLONE between two files of this server: the saved
|
|
// handle is the source, the current one the destination.
|
|
func (h *Handler) cloneOp(d *xdr.Decoder, reg *fhreg) ([]byte, uint32, error) {
|
|
src, err := readStateid(d)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
dst, err := readStateid(d)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
srcOff, err := d.Uint64()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
dstOff, err := d.Uint64()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
count, err := d.Uint64()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
if !reg.haveCur || reg.saved == nil {
|
|
return nil, nfs4.ErrNoFileHandle, nil
|
|
}
|
|
if status := h.checkOpStateid(src, reg.saved, reg.clientID); status != nfs4.ErrOK {
|
|
return nil, status, nil
|
|
}
|
|
if status := h.checkOpStateid(dst, reg.cur, reg.clientID); status != nfs4.ErrOK {
|
|
return nil, status, nil
|
|
}
|
|
// The offsets are signed on the wire in effect: a value beyond the
|
|
// signed range cannot name a byte of any file this server serves,
|
|
// and a count beyond the read limit would size one allocation from
|
|
// the request. Both are refused before anything is read.
|
|
if srcOff > 1<<62 || dstOff > 1<<62 || count > nfs4.DefaultLimits.MaxRead {
|
|
return nil, nfs4.ErrInval, nil
|
|
}
|
|
srcInfo, gerr := h.FS.Getattr(reg.saved)
|
|
if gerr != nil {
|
|
return nil, mapErr(gerr), nil
|
|
}
|
|
dstInfo, gerr := h.FS.Getattr(reg.cur)
|
|
if gerr != nil {
|
|
return nil, mapErr(gerr), nil
|
|
}
|
|
// The ends are checked without addition, so a wrapped sum can never
|
|
// slip past the size guard.
|
|
if uint64(srcInfo.Size) < srcOff || uint64(srcInfo.Size)-srcOff < count ||
|
|
uint64(dstInfo.Size) < dstOff || uint64(dstInfo.Size)-dstOff < count {
|
|
return nil, nfs4.ErrInval, nil
|
|
}
|
|
// CLONE is the reflink of the NFSv4.2 world: where the filesystem
|
|
// provides it the kernel shares the bytes, and where it does not the
|
|
// userspace copy answers instead, the same result either way.
|
|
if cl, ok := h.FS.(nfsfs.RangeCloner); ok {
|
|
if err := cl.CloneRange(reg.saved, int64(srcOff), reg.cur, int64(dstOff), int64(count)); err == nil {
|
|
return nil, nfs4.ErrOK, nil
|
|
}
|
|
}
|
|
data, rerr := h.FS.Read(reg.saved, int64(srcOff), int(count))
|
|
if rerr != nil {
|
|
return nil, mapErr(rerr), nil
|
|
}
|
|
w := h.writer()
|
|
if w == nil {
|
|
return nil, nfs4.ErrROFS, nil
|
|
}
|
|
if _, werr := w.Write(reg.cur, int64(dstOff), data); werr != nil {
|
|
return nil, mapErr(werr), nil
|
|
}
|
|
return nil, nfs4.ErrOK, nil
|
|
}
|
|
|
|
// layoutErrorOp serves LAYOUTERROR: the report is recorded and answered.
|
|
func (h *Handler) layoutErrorOp(d *xdr.Decoder, reg *fhreg) ([]byte, uint32, error) {
|
|
if _, err := d.Uint64(); err != nil { // offset
|
|
return nil, 0, err
|
|
}
|
|
if _, err := d.Uint64(); err != nil { // length
|
|
return nil, 0, err
|
|
}
|
|
if _, err := readStateid(d); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
n, err := d.Uint32()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
for range n {
|
|
if _, err = d.Raw(16); err != nil { // device id
|
|
return nil, 0, err
|
|
}
|
|
if _, err = d.Uint32(); err != nil { // status
|
|
return nil, 0, err
|
|
}
|
|
if _, err = d.Uint32(); err != nil { // opnum
|
|
return nil, 0, err
|
|
}
|
|
}
|
|
if !reg.haveCur {
|
|
return nil, nfs4.ErrNoFileHandle, nil
|
|
}
|
|
return nil, nfs4.ErrOK, nil
|
|
}
|
|
|
|
// layoutStatsOp serves LAYOUTSTATS: the counters are accepted and the
|
|
// call answered.
|
|
func (h *Handler) layoutStatsOp(d *xdr.Decoder, reg *fhreg) ([]byte, uint32, error) {
|
|
for range 2 {
|
|
if _, err := d.Uint64(); err != nil { // offset, length
|
|
return nil, 0, err
|
|
}
|
|
}
|
|
if _, err := readStateid(d); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
for range 4 {
|
|
if _, err := d.Uint64(); err != nil { // io_info4 pairs
|
|
return nil, 0, err
|
|
}
|
|
}
|
|
if _, err := d.Raw(16); err != nil { // device id
|
|
return nil, 0, err
|
|
}
|
|
if _, err := d.Uint32(); err != nil { // layout update type
|
|
return nil, 0, err
|
|
}
|
|
if _, err := d.VarOpaque(); err != nil { // layout update body
|
|
return nil, 0, err
|
|
}
|
|
if !reg.haveCur {
|
|
return nil, nfs4.ErrNoFileHandle, nil
|
|
}
|
|
return nil, nfs4.ErrOK, nil
|
|
}
|
|
|
|
// readPlusOp serves READ_PLUS: this backend has no sparse knowledge on
|
|
// the read path, so the answer is one data segment.
|
|
func (h *Handler) readPlusOp(d *xdr.Decoder, reg *fhreg) ([]byte, uint32, error) {
|
|
st, err := readStateid(d)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
offset, err := d.Uint64()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
count, err := d.Uint32()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
if !reg.haveCur {
|
|
return nil, nfs4.ErrNoFileHandle, nil
|
|
}
|
|
if status := h.checkOpStateid(st, reg.cur, reg.clientID); status != nfs4.ErrOK {
|
|
return nil, status, nil
|
|
}
|
|
if uint64(count) > nfs4.DefaultLimits.MaxRead {
|
|
count = uint32(nfs4.DefaultLimits.MaxRead)
|
|
}
|
|
data, rerr := h.FS.Read(reg.cur, int64(offset), int(count))
|
|
if rerr != nil {
|
|
return nil, mapErr(rerr), nil
|
|
}
|
|
eof := false
|
|
if info, gerr := h.FS.Getattr(reg.cur); gerr == nil {
|
|
eof = int64(offset)+int64(len(data)) >= info.Size
|
|
}
|
|
return nfs4.AppendReadPlusDataRes(nil, eof, offset, data), nfs4.ErrOK, nil
|
|
}
|
|
|
|
// writeSameOp serves WRITE_SAME: the application data block pattern is
|
|
// repeated over the block count at the offset.
|
|
func (h *Handler) writeSameOp(d *xdr.Decoder, reg *fhreg) ([]byte, uint32, error) {
|
|
st, err := readStateid(d)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
stable, err := d.Uint32()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
offset, err := d.Uint64()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
blockSize, err := d.Uint64()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
blockCount, err := d.Uint64()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
if _, err = d.Uint64(); err != nil { // adb_reloff_blocknum
|
|
return nil, 0, err
|
|
}
|
|
if _, err = d.Uint32(); err != nil { // adb_block_num, count4
|
|
return nil, 0, err
|
|
}
|
|
if _, err = d.Uint64(); err != nil { // adb_reloff_pattern
|
|
return nil, 0, err
|
|
}
|
|
pattern, err := d.VarOpaque()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
_ = stable
|
|
if !reg.haveCur {
|
|
return nil, nfs4.ErrNoFileHandle, nil
|
|
}
|
|
if status := h.checkOpStateid(st, reg.cur, reg.clientID); status != nfs4.ErrOK {
|
|
return nil, status, nil
|
|
}
|
|
// The block size and the total the operation may write are bounded
|
|
// before anything is allocated: a wire controlled size beyond the
|
|
// write limit is refused, never used as an allocation length.
|
|
if blockSize == 0 || blockCount == 0 || len(pattern) == 0 {
|
|
return nil, nfs4.ErrInval, nil
|
|
}
|
|
if blockSize > nfs4.DefaultLimits.MaxWrite || blockCount > 1<<20 ||
|
|
blockSize*blockCount > nfs4.DefaultLimits.MaxWrite {
|
|
return nil, nfs4.ErrInval, nil
|
|
}
|
|
w := h.writer()
|
|
if w == nil {
|
|
return nil, nfs4.ErrROFS, nil
|
|
}
|
|
block := make([]byte, 0, blockSize)
|
|
for len(block) < int(blockSize) {
|
|
block = append(block, pattern...)
|
|
}
|
|
block = block[:blockSize]
|
|
total := int64(0)
|
|
for range blockCount {
|
|
if _, err := w.Write(reg.cur, int64(offset)+total, block); err != nil {
|
|
return nil, mapErr(err), nil
|
|
}
|
|
total += int64(blockSize)
|
|
}
|
|
return nfs4.AppendWriteSameRes(nil, uint64(total), nfs4.NfsSyncFileSync, h.writeVerifier()), nfs4.ErrOK, nil
|
|
}
|