tl;dr
Need to be able to use errors.Is(clientError, linux.Errno(someVal)) in code that uses Client,
Long version:
internal and internal/linux pkgs can't be used by calling code, which makes handling errors client-side difficult, especially on non-Linux.
Consider a server and client on a Windows host, and a client-side API which wants to wrap Create.
Server-side, Walk and Create methods seem expected to return the os.ErrNotExist and os.ErrExist values,
which get translated by clientFile internally via newErr/internal.ExtractErrno into linux.ENOENT and linux.EEXIST.
Client-side, whether you use Walk to check file existence before sending Create, or send Create alone; there's no easy way to distinguish which specific error you received.
The workaround I'm currently using requires copying the (protocol-level) error values into an accessible pkg, and then using the runtime to extract the underlying decimal values from Client errors for comparison. Which seems less than ideal.
Here's an example from a toy/prototype that might demonstrate this.
(I can bundle this whole thing up if needed, but it's just for experimenting/exercising this library right now so it's gross)
This errors pkg contains direct copies of the internal linux.Errno type and values.
goerrors is the standard errors pkg.
(errors is a bad name on my part and should probably be something like perrors for protocol errors or something)
func makeMessage(dir p9.File, filename string) (p9.File, error) {
const flags = p9.WriteOnly
wnames := []string{filename}
_, messageFile, err := dir.Walk(wnames)
if err != nil {
err = runtimeHax(err)
if !goerrors.Is(err, errors.ENOENT) {
return nil, err
}
_, dirClone, err := dir.Walk(nil)
if err != nil {
return nil, err
}
messageFile, _, _, err := dirClone.Create(filename, flags,
p9.ModeRegular|p9.AllPermissions, p9.NoUID, p9.NoGID)
return messageFile, err
}
// TODO: check mode attr isregular
// TODO: truncate/setattr on open
if _, _, err := messageFile.Open(flags); err != nil {
return nil, err
}
return messageFile, nil
}
func runtimeHax(err error) errors.Errno {
// XXX: This works but should basically be considered illegal.
// type notError struct{ l, h unsafe.Pointer }
// return *(*errors.Errno)((*notError)(unsafe.Pointer(&err)).h)
// XXX: Better, not best. Still not safe.
// We'll panic if `err`'s underlying type changes from `uintptr`, and/or need more complicated handling.
return errors.Errno(reflect.ValueOf(err).Uint())
}
It'd be ideal if the client could call errors.Is(err, linux.ENOENT) without needing err = runtimeHax(err).
As an aside, there's a subtle discrepancy here which may or may not matter.
Calling server methods directly will return host-native error values (os.ErrExist) while calling the method via Client return the protocol-native values (linux.EEXIST).
This makes sense and should probably be expected, but I'm mentioning it anyway since calling code may have to consider both host-native and protocol-level error values, despite wrapping the same methods.
Although it may be expected to always utilize Client even when in the same process(, using some memcopy-like transport).
And this is kind of out of scope of the problem.
In addition to this, server implementations can't return protocol-level error numbers because linux.Errno is not exported.
ExtractErrno will check for this type and pass it through to the client if err is one, but there's no way for a server implementation to actually return one. As a result returning something like myError(0x11) always becomes linux.EIO in Client methods.
tl;dr Need to be able to use
errors.Is(clientError, linux.Errno(someVal))
in code that usesClient
,Long version:
internal
andinternal/linux
pkgs can't be used by calling code, which makes handling errors client-side difficult, especially on non-Linux.Consider a server and client on a Windows host, and a client-side API which wants to wrap
Create
.Server-side,
Walk
andCreate
methods seem expected to return theos.ErrNotExist
andos.ErrExist
values, which get translated byclientFile
internally vianewErr
/internal.ExtractErrno
intolinux.ENOENT
andlinux.EEXIST
.Client-side, whether you use
Walk
to check file existence before sendingCreate
, or sendCreate
alone; there's no easy way to distinguish which specific error you received.The workaround I'm currently using requires copying the (protocol-level) error values into an accessible pkg, and then using the runtime to extract the underlying decimal values from
Client
errors for comparison. Which seems less than ideal.Here's an example from a toy/prototype that might demonstrate this. (I can bundle this whole thing up if needed, but it's just for experimenting/exercising this library right now so it's gross)
This
errors
pkg contains direct copies of the internallinux.Errno
type and values.goerrors
is the standarderrors
pkg. (errors
is a bad name on my part and should probably be something likeperrors
for protocol errors or something)It'd be ideal if the client could call
errors.Is(err, linux.ENOENT)
without needingerr = runtimeHax(err)
.As an aside, there's a subtle discrepancy here which may or may not matter. Calling server methods directly will return host-native error values (
os.ErrExist
) while calling the method viaClient
return the protocol-native values (linux.EEXIST
).This makes sense and should probably be expected, but I'm mentioning it anyway since calling code may have to consider both host-native and protocol-level
error
values, despite wrapping the same methods. Although it may be expected to always utilizeClient
even when in the same process(, using some memcopy-like transport). And this is kind of out of scope of the problem.In addition to this, server implementations can't return protocol-level error numbers because
linux.Errno
is not exported.ExtractErrno
will check for this type and pass it through to the client iferr
is one, but there's no way for a server implementation to actually return one. As a result returning something likemyError(0x11)
always becomeslinux.EIO
inClient
methods.