Repository navigation
Expand file tree
/
Copy patherrors.go
More file actions
324 lines (297 loc) · 15.4 KB
/
Copy patherrors.go
File metadata and controls
324 lines (297 loc) · 15.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
// Copyright 2026 gomaja. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package sctp
import (
"errors"
"fmt"
"io"
"net"
"os"
"sync"
"syscall"
)
// The ten error variables. A call that reports one of them wraps it, in the
// *net.OpError of the call or with detail of its own, so compare with
// errors.Is, never with ==.
var (
// ErrUnsupported is what socket-backed calls report on a platform
// without SCTP: a non-Linux GOOS, or Linux with the SCTP module absent
// and not loadable, where the error also matches the kernel's
// EPROTONOSUPPORT or ESOCKTNOSUPPORT. It wraps errors.ErrUnsupported, so
// errors.Is(err, errors.ErrUnsupported) holds too. Config.FragmentInterleave
// and SetFragmentInterleave also report it, for InterleaveStreams, the
// level Linux cannot keep.
ErrUnsupported = fmt.Errorf("sctp: socket operation unsupported: %w", errors.ErrUnsupported)
// ErrMessageTooLong is ReadMsg's report that a message exceeded the
// caller's max: the rest of the record is drained from the kernel so the
// next ReadMsg starts at the next message.
ErrMessageTooLong = errors.New("sctp: message exceeds the requested maximum")
// ErrMessageInterrupted is ReadMsg's report that it could not reach the
// end of a message: the association ended, a deadline passed or the
// connection was closed partway through. ReadMsg returns what it read,
// and aborts the connection, whose receive queue could otherwise be
// misread from there.
ErrMessageInterrupted = errors.New("sctp: could not reach end of message")
// ErrNotificationTooLong is ParseNotification's report that a
// notification's declared length exceeds NotificationReassemblyLimit.
ErrNotificationTooLong = errors.New("sctp: notification exceeds the reassembly limit")
// ErrShortNotification is ParseNotification's report that a notification
// is shorter than its fixed header, or than the fixed portion its type
// declares.
ErrShortNotification = errors.New("sctp: notification shorter than its fixed header")
// ErrControlTruncated reports that the kernel truncated a message's
// ancillary data (MSG_CTRUNC), so RcvInfo or a notification could not be
// read in full.
ErrControlTruncated = errors.New("sctp: ancillary data truncated")
// ErrInvalidRcvInfo reports that the SCTP_RCVINFO record of a message
// an Endpoint received cannot name one association: it names a
// reserved scope selector (SCTP_FUTURE_ASSOC, SCTP_CURRENT_ASSOC,
// SCTP_ALL_ASSOC) instead of a real association id, or it is short,
// repeated or malformed.
ErrInvalidRcvInfo = errors.New("sctp: SCTP_RCVINFO names no association")
// ErrMissingRcvInfo reports that data arrived on an Endpoint without
// SCTP_RCVINFO, so RecvMsg cannot report which association it belongs
// to.
ErrMissingRcvInfo = errors.New("sctp: endpoint data arrived without SCTP_RCVINFO")
// ErrAssocListTooLarge reports that Endpoint.AssocIDs found more
// associations than a bounded read can return.
ErrAssocListTooLarge = errors.New("sctp: association id list too large")
// ErrInvalidAssocList reports that SCTP_GET_ASSOC_ID_LIST returned a
// buffer whose declared count does not fit what the kernel wrote.
ErrInvalidAssocList = errors.New("sctp: association id list malformed")
)
// invalidArgError is invalidArg's return type. Its Error is exactly the
// formatted message — nothing from syscall.EINVAL is appended to it — and its
// Unwrap makes errors.Is(err, syscall.EINVAL) hold.
type invalidArgError struct{ msg string }
func (e *invalidArgError) Error() string { return e.msg }
func (e *invalidArgError) Unwrap() error { return syscall.EINVAL }
// invalidArg builds a validation error for a Config field, SendOptions field
// or method argument the package refuses before any system call. The
// message names what was refused, for example "sctp:
// Config.DelayedSACK.Delay 600ms exceeds RFC 9260 §6.2's 500 ms maximum",
// and matches syscall.EINVAL.
func invalidArg(format string, args ...any) error {
return &invalidArgError{msg: "sctp: " + fmt.Sprintf(format, args...)}
}
// fileClosingErrOnce lazily captures internal/poll's ErrFileClosing sentinel
// ("use of closed file"): the error a syscall.RawConn method (Control,
// Read, Write) returns once the os.File it came from has been closed
// elsewhere, bypassing the os.File's own "already closed" check (which
// gives os.ErrClosed instead — see isFileClosingErr). internal/poll is
// internal to the standard library, so its error cannot be imported or
// type-asserted; capturing the actual value, via capturePipeClosingErr
// below, is the only way to compare against it. It is a plain, comparable
// *errors.errorString — the same package-level value every closed file's
// RawConn returns, confirmed empirically on both darwin and linux — so
// errors.Is finds it through any wrapping.
//
// It matches neither net.ErrClosed (poll.errNetClosing{}, a value, not a
// pointer — "use of closed network connection", what a closed *net.TCPConn
// or similar net.Conn reports) nor os.ErrClosed (fs.ErrClosed, "file
// already closed", what a plain *os.File's own Read/Write/Close report
// once Close has been called through that same *os.File). A closed
// *os.File never goes through the net package's wrapping at all — it
// reports one of the other two, os.ErrClosed or this one, depending on
// which code path is used, never net.ErrClosed. The three exist because
// Go's standard library has three different closed-descriptor code paths,
// and this package's sockets — wrapped in an *os.File like any plain file,
// not through the net package — can hit any of the three; opError and
// optError collapse all three to one net.ErrClosed wrap.
//
// The sentinel reaches this package because two groups of *os.File methods
// return it unconverted, where Read, Write and Close report os.ErrClosed:
// the RawConn methods, and SetDeadline, SetReadDeadline and
// SetWriteDeadline. https://github.com/golang/go/issues/82229 tracks both.
// Once go.mod's go directive requires a Go release in which all of them
// report os.ErrClosed, the os.ErrClosed check next to each isFileClosingErr
// call covers this case, and fileClosingErrOnce, capturePipeClosingErr and
// isFileClosingErr can be removed. capturePipeClosingErr takes the sentinel
// from RawConn.Control, so a release that converted only the RawConn
// methods would leave the deadline setters' sentinel unmatched, and a
// Listener's SetDeadline after Close would stop reporting net.ErrClosed.
// closedPipeRawConnError fails on any toolchain whose RawConn methods
// report os.ErrClosed, which flags that change.
//
// The capture is lazy — sync.OnceValue runs capturePipeClosingErr on the
// first call to isFileClosingErr, not at package init — so importing this
// package never opens a pipe on its own; only the first real closed-socket
// check does.
var fileClosingErrOnce = sync.OnceValue(capturePipeClosingErr)
// capturePipeClosingErr triggers internal/poll's ErrFileClosing by closing
// one end of a pipe and then using its already-taken syscall.RawConn: the
// file's poll.FD is marked closing before Close returns, so Control's own
// reference-count check (poll.FD.incref, checked empirically against both
// darwin and linux) fails and returns that sentinel directly, before it
// would ever run the callback. A failure anywhere in this setup (no pipe
// available) leaves the result nil; isFileClosingErr then falls back to
// matching the sentinel's known text instead of the value itself.
func capturePipeClosingErr() error {
r, w, err := os.Pipe()
if err != nil {
return nil
}
raw, err := r.SyscallConn()
if err != nil {
_ = r.Close()
_ = w.Close()
return nil
}
_ = r.Close()
_ = w.Close()
return raw.Control(func(uintptr) {})
}
// isFileClosingErr reports whether err is, or wraps, internal/poll's
// ErrFileClosing sentinel — including when err is
// os.NewSyscallError(call, thatSentinel), as optError produces, since
// errors.Is follows *os.SyscallError's Unwrap. If capturePipeClosingErr
// could not get a sentinel to compare against (no working pipe on this
// platform or sandbox), this falls back to matching "use of closed file"
// by text at every level of err's own unwrap chain — weaker than a value
// comparison, since two unrelated errors could in principle share that
// text, but better than never recognizing the condition at all.
func isFileClosingErr(err error) bool {
if sentinel := fileClosingErrOnce(); sentinel != nil {
return errors.Is(err, sentinel)
}
for e := err; e != nil; e = errors.Unwrap(e) {
if e.Error() == "use of closed file" {
return true
}
}
return false
}
// opError wraps err for a read, write, message or constructor operation,
// so that the error a caller sees always describes the operation that
// failed — Op, Net, Source and Addr all naming this call, never some
// other one. It never double-wraps an error that already describes this
// exact operation: a *net.OpError already matching op, network, source
// and addr (by their String() forms; a nil Source or Addr matches only a
// nil one) is returned unchanged. Any other *net.OpError — one a
// Config.Control hook or a NotificationHandler returned, from some other
// call entirely — is wrapped as this operation's cause exactly as given,
// so its own Op and Net never leak into a caller reading this one;
// errors.Is and errors.As still reach it and everything it wraps, since
// *net.OpError implements Unwrap. That foreign error's own cause is never
// inspected for net.ErrClosed: the closed-descriptor collapse below is
// for a cause this package's own code reports directly, and running a
// foreign *net.OpError through it too would discard that error's Op and
// Net (and everything else about it) in favour of a bare net.ErrClosed,
// making it unreachable through errors.As. io.EOF is returned unwrapped,
// so err == io.EOF keeps working. For any other, non-*net.OpError cause,
// one matching net.ErrClosed or os.ErrClosed, or one isFileClosingErr
// recognizes — the three different ways the standard library reports a
// closed descriptor — collapses to a single net.ErrClosed wrap, never
// nested and never carrying any of the three's own text. Anything else
// is wrapped as the *net.OpError's cause, unchanged: os.ErrDeadlineExceeded
// already implements net.Error (Timeout, Temporary), so op.Timeout() and
// errors.Is(op, os.ErrDeadlineExceeded) hold with no special case here.
func opError(op, network string, source, addr net.Addr, err error) error {
if err == nil {
return nil
}
if err == io.EOF {
return io.EOF
}
if opErr, ok := err.(*net.OpError); ok {
if sameOpError(opErr, op, network, source, addr) {
return opErr
}
return &net.OpError{Op: op, Net: network, Source: source, Addr: addr, Err: opErr}
}
if errors.Is(err, net.ErrClosed) || errors.Is(err, os.ErrClosed) || isFileClosingErr(err) {
err = net.ErrClosed
}
return &net.OpError{Op: op, Net: network, Source: source, Addr: addr, Err: err}
}
// sameOpError reports whether opErr already describes exactly the
// operation opError is about to build one for: the same Op, the same
// Net, and Source and Addr that compare equal by their String() forms,
// with a nil net.Addr equal only to another nil one (never to a non-nil
// value whose String() happens to render the same way, and never
// dereferenced itself — calling String() on a nil net.Addr interface
// panics, which is what the explicit nil checks below avoid).
func sameOpError(opErr *net.OpError, op, network string, source, addr net.Addr) bool {
return opErr.Op == op &&
opErr.Net == network &&
sameAddr(opErr.Source, source) &&
sameAddr(opErr.Addr, addr)
}
// sameAddr reports whether a and b are both nil, or both non-nil with
// equal String() forms.
func sameAddr(a, b net.Addr) bool {
if a == nil || b == nil {
return a == nil && b == nil
}
return a.String() == b.String()
}
// joinAbortCause joins err with the outcome of the Abort a read's own
// interruption triggered, for a caller (abortInterrupted, recv_linux.go;
// abortInterruptedNotification, endpoint_linux.go) whose result is
// itself about to be wrapped once by opError/ioOpError. aerr is never
// joined in as the *net.OpError it already is: Abort's own error is
// already a *net.OpError with Op "close" (Conn.closeError, close_linux.go),
// and joining
// it whole would put that *net.OpError inside the read's own joined
// error, which the read's later single wrap would then carry as its
// cause — two *net.OpError values in one chain from a path that never
// meant to double-wrap. Its cause (aerr.Err) is joined instead, which
// errors.Is/errors.As still reach through the join. aerr == nil joins
// nothing; aerr matching net.ErrClosed (a released connection, the
// ordinary case) joins the bare sentinel, as before this function
// existed.
func joinAbortCause(err, aerr error) error {
switch {
case aerr == nil:
return err
case errors.Is(aerr, net.ErrClosed):
return errors.Join(err, net.ErrClosed)
}
if opErr, ok := aerr.(*net.OpError); ok {
return errors.Join(err, opErr.Err)
}
return errors.Join(err, aerr)
}
// ioOpError is opError for an error that may join several causes, such as
// an interrupted read's ErrMessageInterrupted and the reason for it: an
// error implementing Unwrap() []error is wrapped whole, without the
// collapse of a closed descriptor's errors into net.ErrClosed, which would
// discard the other causes. Its parts must already be normalised, a closed
// descriptor's error replaced by net.ErrClosed, when they are joined.
// Anything else is wrapped as opError wraps it.
func ioOpError(op, network string, source, addr net.Addr, err error) error {
if _, joined := err.(interface{ Unwrap() []error }); joined {
return &net.OpError{Op: op, Net: network, Source: source, Addr: addr, Err: err}
}
return opError(op, network, source, addr, err)
}
// optError wraps a socket-option failure: a *net.OpError with Op "get" or
// "set" ("bindx" for BindAdd and BindRemove), around
// os.NewSyscallError(call, err), the form net's own option setters use.
// Composing through opError gives the same closed-socket collapse and
// single-*net.OpError guarantee as any other operation: err already
// carrying net.ErrClosed, os.ErrClosed, or the sentinel isFileClosingErr
// recognizes (the descriptor was closed before the syscall was attempted)
// still comes out as one net.ErrClosed wrap. isFileClosingErr's own check
// is errors.Is, which follows *os.SyscallError's Unwrap to find the cause
// this function itself just wrapped, so "get sctp: getsockopt: use of
// closed file" — what os.NewSyscallError("getsockopt", err) alone would
// produce — never reaches the caller; opError's net.ErrClosed replaces err
// before the *net.OpError is built.
func optError(op, call, network string, source, addr net.Addr, err error) error {
return opError(op, network, source, addr, os.NewSyscallError(call, err))
}
// unsupportedErr reports a socket-creation failure the kernel refused
// because SCTP is unavailable: EPROTONOSUPPORT or ESOCKTNOSUPPORT from
// socket() (inet_create, net/ipv4/af_inet.c). It matches ErrUnsupported,
// errors.ErrUnsupported and the errno itself, so a caller can test for any
// of the three.
type unsupportedErr struct{ errno syscall.Errno }
func (e unsupportedErr) Error() string {
return fmt.Sprintf("sctp: socket operation unsupported: %s", e.errno)
}
// Unwrap exposes both ErrUnsupported (which itself wraps errors.ErrUnsupported)
// and the errno to errors.Is and errors.As, via Go's multi-error unwrapping.
func (e unsupportedErr) Unwrap() []error {
return []error{ErrUnsupported, e.errno}
}