New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Standardize error checking across libbpfgo #100
Standardize error checking across libbpfgo #100
Conversation
libbpfgo.go
Outdated
return fmt.Errorf("failed to load BPF object") | ||
cErr := C.bpf_object__load(m.obj) | ||
if cErr != 0 { | ||
return fmt.Errorf("failed to load BPF object", syscall.Errno(int(-cErr))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this right ? Follow my thoughts:
You call bpf_object__load(NULL)... then you will face:
if (!attr)
return libbpf_err(-EINVAL);
and libbpf_err(-EINVAL):
:
static inline int libbpf_err(int ret)
{
if (ret < 0)
errno = -ret;
return ret;
}
So it will return a negative number, but errno will still be positive on the error return. Why do you need to set it to negative again ? And also it is supposed to be an unsigned integer:
type Errno uintptr
and all general errnos are positive as well:
// Errors
const (
E2BIG = Errno(0x7)
EACCES = Errno(0xd)
EADDRINUSE = Errno(0x62)
EADDRNOTAVAIL = Errno(0x63)
EADV = Errno(0x44)
EAFNOSUPPORT = Errno(0x61)
EAGAIN = Errno(0xb)
EALREADY = Errno(0x72)
EBADE = Errno(0x34)
EBADF = Errno(0x9)
EBADFD = Errno(0x4d)
EBADMSG = Errno(0x4a)
EBADR = Errno(0x35)
EBADRQC = Errno(0x38)
EBADSLT = Errno(0x39)
EBFONT = Errno(0x3b)
EBUSY = Errno(0x10)
ECANCELED = Errno(0x7d)
ECHILD = Errno(0xa)
ECHRNG = Errno(0x2c)
ECOMM = Errno(0x46)
ECONNABORTED = Errno(0x67)
ECONNREFUSED = Errno(0x6f)
ECONNRESET = Errno(0x68)
EDEADLK = Errno(0x23)
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, shouldn't you have a %s to stringify the errno ? Like shown at:
func (e Errno) Error() string {
if 0 <= int(e) && int(e) < len(errors) {
s := errors[e]
if s != "" {
return s
}
}
return "errno " + itoa.Itoa(int(e))
}
Was that your intent here ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are other places doing the same thing. I'll clarify this first before moving on the review (as I'd like to understand the reasoning of that).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok you are correct, I didn't mean to have negatives there, that was an inconsistency and mistake :-)
Also yes, %s should be used since the error
interface implements stringer. Just missing the %s here.
Relevant mailing list question: https://lore.kernel.org/bpf/CAO658oXGs3R=mQZo=aOEvRgG6O-obFbwR54V+QSn36uecF0+=w@mail.gmail.com/ |
Hey @grantseltzer I think there's some inconsistencies with the error handling in libbpf (at least that's what I'm observing). If you check the other PR I submitted (#101) you can see |
Also I believe some of Andrii's comments to be mistaken in his review on your documentation PR: https://patchwork.kernel.org/project/netdevbpf/patch/20211220054048.54845-1-grantseltzer@gmail.com/. He claims that certain buffers for input buffers, such as the |
@derekparker Are you saying that |
Ack my mistake I think you're right. |
@grantseltzer in my opinion, based on what I've seen during implementation (return value differing from errno in some cases) I think what this library should do is return the errno error wrapped in another error, so that callers can use patterns such as:
That would mean in the implementation we would use a pattern of:
Note the Thoughts? I could test this change locally against my codebase but I'm pretty sure this would be sufficient and would standardize errors. |
@derekparker I think that's a good idea, makes a lot of sense. I believe the inconsistencies between returned error and errno values is going to be solved by the time libbpf 1.0 is released, but i'm not entirely sure. Will verify and report back, getting back into the swing of things after vacation at the moment! I hope you had a nice holiday! |
Ack, thanks for the response! And likewise, still getting my head back in the game, haha. Hope you had a nice holiday as well! |
libbpfgo.go
Outdated
@@ -672,7 +661,7 @@ func (b *BPFMap) UpdateBatch(keys, values unsafe.Pointer, count uint32) error { | |||
} | |||
errC := C.bpf_map_update_batch(b.fd, keys, values, &countC, bpfMapBatchOptsToC(&opts)) | |||
if errC != 0 { | |||
return fmt.Errorf("failed to update map %s: %v", b.name, errC) | |||
return fmt.Errorf("failed to update map %s: %w", b.name, syscall.Errno(int(errC))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should wrap the errno value directly, due to inconsistencies with the returned value and errno in libbpf.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough! I investigated and it looks like not every API function is directly moved over to directly returning error codes yet. Errno is better to rely on for now.
Only one comment otherwise this LGTM. I just think we should explicitly check errno and wrap that to not have to deal with inconsistencies between ret value and errno value. |
This handles correcting how we check errors in libbpfgo in accordance with how libbpf recommends handling errors leading into libbpf. Particularly this follows the following rules: - APIs that return an error code (int) should have error codes checked directly. The error codes correspond with error codes in the syscall package. For example: ``` errCodeInt := C.libbpf_api_function() if errCodeInt != 0 { log.Errorf("uh oh: %s\n", syscall.Errno(errCodeInt)) } ``` - APIs that return a pointer should be checked for NULL which indicates error. The error code is stored in errno. We can get the value of errno using the second return. For example: ``` ptr, errno := C.libbpf_api_function() if ptr == nil { log.Errorf("uh oh: %s\n", errno) } ``` We can also check if errno corresponds with a specific error (it implements the standard error interface). For example: ``` ptr, errno := C.libbpf_api_function() if ptr == nil { if errno.Is(syscall.ENODENT) { // handle accordingly } else { log.Errorf("uh oh: %s\n", errno) } } ``` Signed-off-by: grantseltzer <grantseltzer@gmail.com>
Signed-off-by: grantseltzer <grantseltzer@gmail.com>
Signed-off-by: grantseltzer <grantseltzer@gmail.com>
Signed-off-by: grantseltzer <grantseltzer@gmail.com>
fca71b7
to
ca51024
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Signed-off-by: grantseltzer <grantseltzer@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
if ret < 0 { | ||
return syscall.Errno(-ret) | ||
if ret != 0 { | ||
return syscall.Errno(ret) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
syscall.Errno expect a positive value to get the correct error - are you sure it is correct to remove the negative sign? (also for all of the above)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You call bpf_object__load(NULL)... then you will face:
if (!attr) return libbpf_err(-EINVAL);and
libbpf_err(-EINVAL):
:static inline int libbpf_err(int ret) { if (ret < 0) errno = -ret; return ret; }So it will return a negative number, but errno will still be positive on the error return. Why do you need to set it to negative again ?
Like you wrote here, the return is a negative number, meaning ret is negative. To have a positive value, we should syscall.Errno(-ret)
if C.IS_ERR_OR_NULL(unsafe.Pointer(obj)) { | ||
return nil, errptrError(unsafe.Pointer(obj), "failed to open BPF object %s", args.BPFObjPath) | ||
obj, errno := C.bpf_object__open_file(bpfFile, &opts) | ||
if obj == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will only be null if LIBBPF_STRICT_CLEAN_PTRS is set.
https://github.com/libbpf/libbpf/blob/e99f34e14447866bcbc38944387531c36f6c85fe/src/libbpf_internal.h#L476
Is that the case?
This reverts commit 088c639.
This handles correcting how we check errors in libbpfgo
in accordance with how libbpf recommends handling errors
leading into libbpf.
Particularly this follows the following rules:
APIs that return an error code (int) should have
error codes checked directly. The error codes
correspond with error codes in the syscall package.
For example:
APIs that return a pointer should be checked for
NULL which indicates error. The error code is
stored in errno. We can get the value of errno
using the second return.
For example:
We can also check if errno corresponds with a specific
error (it implements the standard error interface).
For example:
Signed-off-by: grantseltzer grantseltzer@gmail.com