Understanding Ruby's strange "Errno" exceptions
If you've ever taken a look at Ruby's exception hierarchy, you may have noticed something weird. In addition to all of the normal exceptions like RuntimeError and NoMethodError, there's an odd reference to
Exception StandardError ... SystemCallError Errno::* ...
If you've ever had the bad luck to write to disk when the disk is full, or to try to make an API call over a failing network then you've probably seen this type of error in action. You can trigger one right now by attempting to open a file that doesn't exist.
irb> File.open("badfilename.txt") Errno::ENOENT: No such file or directory @ rb_sysopen - badfilename.txt from (irb):9:in `initialize' from (irb):9:in `open' from (irb):9 from /Users/snhorne/.rbenv/versions/2.1.0/bin/irb:11:in `<main>'
But what exactly are the Errono exceptions? And why are they treated differently thank other kinds of exceptions?
Adapting Ruby to the OS
The Errno exceptions are essentially an adapter. They connect operating system errors to Ruby's exception system. The operating system handles errors in a different way than Ruby does, so you have to have some kind of adapter.
In ruby, errors tend to be reported as exceptions. But operating system errors are usually just integers. So ruby defines one exception class for each possible OS error. It then sticks all of these exceptions into a module called Errno.
We can use IRB to see all of the exceptions in this module. And boy, are there a lot!
irb> Errno.constants => [:NOERROR, :EPERM, :ENOENT, :ESRCH, :EINTR, :EIO, :ENXIO, :E2BIG, :ENOEXEC, :EBADF, :ECHILD, :EAGAIN, :ENOMEM, :EACCES, :EFAULT, :ENOTBLK, :EBUSY, :EEXIST, :EXDEV, :ENODEV, :ENOTDIR, :EISDIR, :EINVAL, :ENFILE, :EMFILE, :ENOTTY, :ETXTBSY, :EFBIG, :ENOSPC, :ESPIPE, :EROFS, :EMLINK, :EPIPE, :EDOM, :ERANGE, :EDEADLK, :ENAMETOOLONG, :ENOLCK, :ENOSYS, :ENOTEMPTY, :ELOOP, :EWOULDBLOCK, :ENOMSG, :EIDRM, :ECHRNG, :EL2NSYNC, :EL3HLT, :EL3RST, :ELNRNG, :EUNATCH, :ENOCSI, :EL2HLT, :EBADE, :EBADR, :EXFULL, :ENOANO, :EBADRQC, :EBADSLT, :EDEADLOCK, :EBFONT, :ENOSTR, :ENODATA, :ETIME, :ENOSR, :ENONET, :ENOPKG, :EREMOTE, :ENOLINK, :EADV, :ESRMNT, :ECOMM, :EPROTO, :EMULTIHOP, :EDOTDOT, :EBADMSG, :EOVERFLOW, :ENOTUNIQ, :EBADFD, :EREMCHG, :ELIBACC, :ELIBBAD, :ELIBSCN, :ELIBMAX, :ELIBEXEC, :EILSEQ, :ERESTART, :ESTRPIPE, :EUSERS, :ENOTSOCK, :EDESTADDRREQ, :EMSGSIZE, :EPROTOTYPE, :ENOPROTOOPT, :EPROTONOSUPPORT, :ESOCKTNOSUPPORT, :EOPNOTSUPP, :EPFNOSUPPORT, :EAFNOSUPPORT, :EADDRINUSE, :EADDRNOTAVAIL, :ENETDOWN, :ENETUNREACH, :ENETRESET, :ECONNABORTED, :ECONNRESET, :ENOBUFS, :EISCONN, :ENOTCONN, :ESHUTDOWN, :ETOOMANYREFS, :ETIMEDOUT, :ECONNREFUSED, :EHOSTDOWN, :EHOSTUNREACH, :EALREADY, :EINPROGRESS, :ESTALE, :EUCLEAN, :ENOTNAM, :ENAVAIL, :EISNAM, :EREMOTEIO, :EDQUOT, :ECANCELED, :EKEYEXPIRED, :EKEYREJECTED, :EKEYREVOKED, :EMEDIUMTYPE, :ENOKEY, :ENOMEDIUM, :ENOTRECOVERABLE, :EOWNERDEAD, :ERFKILL, :EAUTH, :EBADRPC, :EDOOFUS, :EFTYPE, :ENEEDAUTH, :ENOATTR, :ENOTSUP, :EPROCLIM, :EPROCUNAVAIL, :EPROGMISMATCH, :EPROGUNAVAIL, :ERPCMISMATCH, :EIPSEC]
But why are they named so cryptically? I mean, how am I supposed to ever guess that ENOINT means "File not found?"
...There's actually a very simple answer.
Copied wholesale from libc
Whoever first built the Errno module just copied the error names directly from libc. So ENOINT, in C, is the name of a macro which contains the integer error code that the OS returns when it can't find a file.
So, to really find out what each of these does the trick is to look at the documentation for the C standard library. You can find a big list of them here. I've excerpted a few of the more relevant ones below:
|EPERM||Operation not permitted; you can't access the file unless you have permission.|
|ENOENT||File or directory not found.|
|EIO||Input/output error; usually used for physical read or write errors.|
|EBADF||Bad file descriptor. You would get this error if you tried to write to a file you opened only for reading, for example.|
|ECHILD||You tried to manipulate a child process, but there aren't any child processes.|
|ENOMEM||You're out of RAM, and can't allocate any more virtual memory.|
|EACCES||Permission denied; the file permissions do not allow the attempted operation.|
|ENOTBLK||You tried to mount an ordinary file as a device, like a HDD.|
|EBUSY||Resource busy; a system resource that can’t be shared is already in use. For example, if you try to delete a file that is the root of a currently mounted filesystem, you get this error.|
|EEXIST||File exists; an existing file was specified in a context where it only makes sense to specify a new file.|
|ENOTDIR||A file that isn’t a directory was specified when a directory is required.|
|EISDIR||File is a directory; you cannot open a directory for writing, or create or remove hard links to it.|
|EINVAL||Invalid argument. This is used to indicate various kinds of problems with passing the wrong argument to a library function.|
|EMFILE||The current process has too many files open and can’t open any more. Duplicate descriptors do count toward this limit.|
|EFBIG||File too big; the size of a file would be larger than allowed by the system.|
|ENOSPC||No space left on device; write operation on a file failed because the disk is full.|
|ESPIPE||Invalid seek operation (such as on a pipe).|
|EROFS||An attempt was made to modify something on a read-only file system.|
|EPIPE||Broken pipe; there is no process reading from the other end of a pipe|
|ENOTSOCK||A file that isn’t a socket was specified when a socket is required.|
|ENETUNREACH||A socket operation failed because the subnet containing the remote host was unreachable.|
|ENETRESET||A network connection was reset because the remote host crashed.|
|ECONNABORTED||A network connection was aborted locally.|
|ECONNRESET||A network connection was closed for reasons outside the control of the local host, such as by the remote machine rebooting or an unrecoverable protocol violation.|
|ENOBUFS||The kernel’s buffers for I/O operations are all in use. In GNU, this error is always synonymous with ENOMEM; you may get one or the other from network operations.|
|EISCONN||You tried to connect a socket that is already connected. See Connecting.|
|ENOTCONN||The socket is not connected to anything. You get this error when you try to transmit data over a socket, without first specifying a destination for the data.|
|EDESTADDRREQ||No default destination address was set for the socket. You get this error when you try to transmit data over a connectionless socket, without first specifying a destination for the data with connect.|
|ESHUTDOWN||The socket has already been shut down.|
|ETIMEDOUT||A socket operation with a specified timeout received no response during the timeout period.|
|ECONNREFUSED||A remote host refused to allow the network connection (typically because it is not running the requested service).|
|EHOSTDOWN||The remote host for a requested network connection is down.|
|EHOSTUNREACH||The remote host for a requested network connection is not reachable.|
|ENOTEMPTY||Directory not empty, where an empty directory was expected. Typically, this error occurs when you are trying to delete a directory.|
|EPROCLIM||This means that the per-user limit on new process would be exceeded by an attempted fork. See Limits on Resources, for details on the RLIMIT_NPROC limit.|