Open squeek502 opened 1 year ago
mismatch seems inherited
EPERM 1 Operation not permitted
EACCES 13 Permission denied
There is no explicit error set that I can find that includes both
PermissionDenied
andAccessDenied
I was wrong. There's at least one:
I’m writing Zig bindings to a C library for a Linux system administration application—this is the lens through which I’m viewing this issue.
I want to keep C code and errno
out of the main application code, so I’ve defined a single error set type to encompass the errors that the C library functions can return out of band via errno
. In my case, errno
can be set to EACCES
but not EPERM
, EPERM
but not EACCES
, or either EACCES
or EPERM
depending on the function that was called.
EACCES
and EPERM
come from the POSIX.1-2001 specification, which has this to say:
[EACCES] Permission denied. An attempt was made to access a file in a way forbidden by its file access permissions.
[EPERM] Operation not permitted. An attempt was made to perform an operation limited to processes with appropriate privileges or to the owner of a file or other resource.
The implication is that EACCES
is specific to the permissions on a given file and EPERM
indicates a generic deficiency of privilege that may not have anything to do with files, e.g., calling setuid(2) to become an arbitrary user without CAP_SETUID
. I could make the argument that a failure to access a file due to insufficient permissions is an instance of an operation that isn’t permitted and that there is therefore no need to distinguish between the two cases.
However, the distinction above turns out to be meaningful for the purpose of troubleshooting my (privileged) application. Thus, I’d like to be able to represent both constants in my error set type. How should I do that? Knowing that, at an upper layer of my application, I will be uniting my library-specific error set type with syscall-specific error set types in std.os
, I would rather use the same representations that the standard library does for EACCES
and EPERM
so as not to end up with redundant errors. Currently, error.AccessDenied
and error.PermissionDenied
, respectively, serve this role.
If there were only one representation for both EACCES
and EPERM
in the standard library, then I would want to introduce my own representation for one of EACCES
and EPERM
to maintain the distinction. However, I may not choose the same representation as the author of another module that is also checking errno
. If I should happen to include their module as a dependency, I may find myself writing boilerplate code to unify multiple representations of what is canonically one thing.
Allow me to give a more concrete example where compressing the distinction away can be undesirable: the bpf(2) syscall. Since bpf(2) is a Linux-specific syscall, there’s no corresponding API in std.os
—users wanting to leverage an idiomatic Zig interface to std.os.linux.bpf
that returns an error union type will need to write their own. They will then encounter the following situation: Not only can bpf(2) set errno
to EACCES
and EPERM
, it uses EACCES
to communicate a specific error that has nothing to do with file access permissions:
EACCES For
BPF_PROG_LOAD
, even though all program instructions are valid, the program has been rejected because it was deemed unsafe. This may be because it may have accessed a disallowed memory region or an uninitialized stack/register or because the function constraints don’t match the actual types or because there was a misaligned memory access. In this case, it is recommended to callbpf
() again withlog_level = 1
and examinelog_buf
for the specific reason provided by the verifier.
Meanwhile, EPERM
is used in the expected manner—to convey the absence of privileges needed to call bpf(2) in the first place:
EPERM The call was made without sufficient privilege (without the
CAP_SYS_ADMIN
capability).
I believe that the standard library should equip users with the constructs they need to handle situations such as these should they deem it meaningful for their use cases.
If anything, my suggestion is to rename PermissionDenied
to OperationNotPermitted
to help dispel conflation with AccessDenied
. The current nomenclature of AccessDenied
and PermissionDenied
confuses me to the same degree as the traditional perror(3) output (“Permission denied” and “Operation not permitted”, respectively).
Nomenclature notwithstanding, I agree that an effort should be made to ensure a consistent mapping from E.ACCES
to error.AccessDenied
and from E.PERM
to error.PermissionDenied
across the standard library for the reasons given above.
Thanks for the excellent analysis, all! It seems to me that what this discussion boils down to is that std.os
needs to choose whether to consistently respect the errno itself or its perror description in translating errnos to zig errors.
I would argue that using the errno to decide error names would be more intuitive for anyone familiar with developing for *nix, would make porting code to zig easier, and minimize the future role of std.os
maintainers. I second error.AccessDenied
for EACCES
and error.PermissionDenied
for EPERM
simply because they are already widely used.
These two error names are common through the Zig codebase, but they seem to be used pretty much interchangeably and don't seem to ever be used to convey unique information, so it seems like one canonical version should be chosen and used everywhere (or a distinction should be made about when to use one over another).
Some evidence for why I think it might make sense to merge them:
There is no explicit error set that I can find that includes bothPermissionDenied
andAccessDenied
, meaning that there is (presumably) nothing that intentionally uses one name over another to convey unique information. Put another way, there wouldn't be any information that is currently conveyed that would be lost if they were merged.error.AccessDenied => return error.PermissionDenied
(and more will be added by https://github.com/ziglang/zig/pull/16783).ACCESS_DENIED => return error.PermissionDenied
and 7 instances of.ACCES => return error.PermissionDenied
.PERM => return error.AccessDenied
In terms of which is more common:
return error.PermissionDenied
return error.AccessDenied