In Progress
Unit 1, Lesson 21
In Progress

Errno

Video transcript & code

So, we try to open a file, but it's not there.

open("NOTEXIST")
# =>

# ~> Errno::ENOENT
# ~> No such file or directory @ rb_sysopen - NOTEXIST
# ~>
# ~> xmptmp-in7940Ca2.rb:1:in `initialize'
# ~> xmptmp-in7940Ca2.rb:1:in `open'
# ~> xmptmp-in7940Ca2.rb:1:in `<main>'

The error that we get is something called Errno::ENOENT.

It turns out that Errno::ENOENT is an exception class derived from the class SystemCallError.

Errno::ENOENT.ancestors
# => [Errno::ENOENT, SystemCallError, StandardError, Exception, Object, JSON:...

ENOENT is not alone. It's part of a whole family of SystemCallErrors, each one corresponding to a potential error code, or "errno", that might be returned from an internal system call when it fails.

What if we wanted to see a list of all the possible system call errors? Since all of these error classes are namespaced within the Errno module, we can easily list them all by asking that module for its list of contained constants.

require "pp"
pp 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,
# >>  :EHWPOISON,
# >>  :ECAPMODE,
# >>  :ENOTCAPABLE]

There are quite a few of these. There's even an EDOOFUS error, which is one I personally raise quite a bit when I haven't had enough coffee.

The name of this group of exception classes is Errno, which implies there are error numbers involved. But so far we haven't seen any numbers.

In fact, every Errno class does have an operating system error number associated with it. To find it, we can look at the Errno constant inside the exception class.

In this case, the error number is 2.

Errno::ENOENT::Errno            # => 2

And yes, this is confusing, on multiple levels. First, the constant name Errno is used for both the top-level namespacing module, and for the error number constants within the individual error classes. Second, it's weird that the error classes have all-caps names; but the numeric constants have class-style camel-case naming.

While we're on the topic of things that are confusing: It's important to note that the error numbers are specific to the operating system that Ruby was compiled for. So for instance some of these error classes may have the same meaning, but a different Errno, on Linux vs on Max OS X.

Some errors have a number of 0. This means that they are errors which don't exist on this operating system. On my system, our friend EDOOFUS is one of these.

Errno::EDOOFUS::Errno           # => 0

But if we were looking at this on a FreeBSD system, we'd see code 88 instead, indicating a programming error.

The names of the error classes all correspond to semi-standardized error mnemonics, many of which are the same across many operating systems. Unfortunately, these mnemonics aren't always the most revealing of their meaning.

When we got our ENOENT error by trying to open a nonexistent file, we also got a nice explanatory string: "No such file or directory".

open("NOTEXIST")
# =>

# ~> Errno::ENOENT
# ~> No such file or directory @ rb_sysopen - NOTEXIST
# ~>
# ~> xmptmp-in7940Ca2.rb:1:in `initialize'
# ~> xmptmp-in7940Ca2.rb:1:in `open'
# ~> xmptmp-in7940Ca2.rb:1:in `<main>'

It's nice to know that when the errors are triggered, we'll see some more information than just the error name. But what if we want to look up what the error codes mean without arranging for the error to actually be raised?

Of course, we could always Google it. But it would be nice to be able to get at this information from within a Ruby session.

Code written in C can do this easily, using a standard C function called strerror(). But surprisingly, as far as I know Ruby does not expose this function directly, anywhere in its standard libraries!

However, there's a sneaky way for us to get the messages corresponding to error classes from within Ruby.

When a SystemCallError is initialized, it takes a a string message and an error number.

SystemCallError.new("My little error", 256)
# => #<SystemCallError: Unknown error 256 - My little error>

But! If we only give it a number, with no message, SystemCallError instead asks the operating system for a corresponding error message.

SystemCallError.new(2)
# => #<Errno::ENOENT: No such file or directory>

We can then ask this error object for its message, and see the information we were looking for.

SystemCallError.new(2).message
# => "No such file or directory"

It's far from the most obvious way to get at error messages, but in a pinch it works.

In an upcoming episode, we'll talk about a way to get this information more directly, by calling out C functions straight from Ruby code.

Happy hacking!

Responses