# Finer-grained file system exceptions

**URL:** <https://racket.discourse.group/t/finer-grained-file-system-exceptions/870>\
**Category:** General\
**Tags:** exceptions\
**Created:** [April 10, 2022, 11:40am UTC](https://racket.discourse.group/t/finer-grained-file-system-exceptions/870 "2022-04-10T11:40:15Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![sschwarzer](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/sschwarzer/32/1940_2.png) [@sschwarzer](https://racket.discourse.group/u/sschwarzer)\
**Post date:** [April 10, 2022, 11:40am UTC](https://racket.discourse.group/t/finer-grained-file-system-exceptions/870/1 "2022-04-10T11:40:15Z")

</div>

Looking at [15.2&nbsp;Filesystem](https://docs.racket-lang.org/reference/Filesystem.html) , most if not all file operations, if they fail, raise `exn:fail:filesystem`. However, what should I do if I need more detailed information?

For example, if I call [`rename-file-or-directory`](https://docs.racket-lang.org/reference/Filesystem.html#%28def._%28%28quote._~23~25kernel%29._rename-file-or-directory%29%29) and an `exn:fail:filesystem` is raised, how could I tell why this call failed? (Was it because the source file wasn't found, was it because I didn't have write permissions for the target directory or something else?)

Some languages (notably C) use error codes to check after the call, which is a bit awkward, but at least some more detailed information is accessible. Another approach would be to raise more specific exceptions for specific error types. (Incidentally, Python used to use only [error codes](https://docs.python.org/3/library/exceptions.html#OSError.errno), but later added [specific exceptions](https://docs.python.org/3/library/exceptions.html#os-exceptions).) Would using more specific exceptions be a feasible approach for Racket, too?

---

<div class="post-metadata">

**Author:** ![alexh](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/alexh/32/315_2.png) [@alexh](https://racket.discourse.group/u/alexh)\
**Post date:** [April 10, 2022, 12:10pm UTC](https://racket.discourse.group/t/finer-grained-file-system-exceptions/870/2 "2022-04-10T12:10:56Z")

</div>

> However, what should I do if I need more detailed information?

```scheme
(with-handlers ((exn:fail? displayln)) 
   (rename-file-or-directory "c:/non-existent" "c:/something-else"))
#(struct:exn:fail:filesystem:errno rename-file-or-directory: cannot rename file or directory
  source path: c:\non-existent
  dest path: c:\something-else
  system error: The system cannot find the file specified.; win_err=2 #<continuation-mark-set> (2 . windows))

```

---

<div class="post-metadata">

**Author:** ![sschwarzer](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/sschwarzer/32/1940_2.png) [@sschwarzer](https://racket.discourse.group/u/sschwarzer)\
**Post date:** [April 10, 2022, 12:46pm UTC](https://racket.discourse.group/t/finer-grained-file-system-exceptions/870/3 "2022-04-10T12:46:23Z")

</div>

Yes, printing the information from a failure is what I'm usually doing for now, so the user can react to that. However, this doesn't help if you _could_ handle certain error situations (but not others) programmatically.

So far, I've gotten away with the approach you mentioned, but it feels limited to me.

Another workaround would be to implement extra checks to distinguish error situations, but this is more code to write and possibly debug, and it's subject to race conditions.

---

<div class="post-metadata">

**Author:** ![alexh](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/alexh/32/315_2.png) [@alexh](https://racket.discourse.group/u/alexh)\
**Post date:** [April 10, 2022, 12:59pm UTC](https://racket.discourse.group/t/finer-grained-file-system-exceptions/870/4 "2022-04-10T12:59:10Z")

</div>

Perhaps my original message was not clear. The exception raised is actually `exn:fail:filesystem:errno` which contains the error code, which is platform specific, so you can filter by that:

```scheme
> (with-handlers
    ((exn:fail:filesystem:errno?
      (lambda (e)
        (match-define (cons errno platform) (exn:fail:filesystem:errno-errno e))
        (printf "Call failed with error code ~a, on platform ~a~" errno platform))))
  (rename-file-or-directory "c:/non-existent" "c:/something-else"))
Call failed with error code 2, on platform windows

```

Or you can catch specific exceptions:

```scheme
(with-handlers
    (((lambda (e)
        (and (exn:fail:filesystem:errno? e)
             (equal? '(2 . windows) (exn:fail:filesystem:errno-errno e))))
      (lambda (e)
        (printf "We only catch windows errors of type \"The system cannot find the file specified\"~%"))))
  (rename-file-or-directory "c:/non-existent" "c:/something-else"))
We only catch windows errors of type "The system cannot find the file specified"

```

Although you are correct that the exception does not contain the system call that failed and its parameters (which are present in the actual exception message.

Alex.

---

<div class="post-metadata">

**Author:** ![sschwarzer](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/sschwarzer/32/1940_2.png) [@sschwarzer](https://racket.discourse.group/u/sschwarzer)\
**Post date:** [April 10, 2022, 6:48pm UTC](https://racket.discourse.group/t/finer-grained-file-system-exceptions/870/5 "2022-04-10T18:48:27Z")

</div>

Ok, this is clearer, thank you! 🙂

I wonder though how usable and reliable this is in practice.

- The documentation of [`exn:fail:filesystem:errno`](https://docs.racket-lang.org/reference/exns.html#%28def._%28%28lib._racket%2Fprivate%2Fbase..rkt%29._exn~3afail~3afilesystem~3aerrno%29%29) only says "Raised for a filesystem error for which a system error code is available." So alone from the documentation, there's no information which _specific_ functions would raise `exn:fail:filesystem:errno` and which functions would raise just `exn:fail:filesystem`. Even if I found by trial and error that a function raises `exn:fail:filesystem:errno`, I wouldn't be sure if that's the case on other platforms or on my development platform in the future.

- It's not clear what the specific error codes for different conditions are. I _guess_ for Windows [these](https://docs.microsoft.com/en-us/cpp/c-runtime-library/errno-constants) are used. Under Posix, I _guess_ what's meant are the constants from `errno.h`.

- Since the errno values are platform-specific, it'll be kind of tedious to handle error codes for different platforms in the Racket code.

- There's no uniform mapping of errno numeric constants and symbolic constants (semantics). Even when comparing the values for the "Posix" platforms Linux ([errno-base.h](https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno-base.h) and [errno.h](https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno.h)), [FreeBSD](https://www.freebsd.org/cgi/man.cgi?query=errno&sektion=2&manpath=freebsd-release-ports) and [Mac OS X/Darwin](https://github.com/apple/darwin-xnu/blob/main/bsd/sys/errno.h), many of the symbolic/numeric pairs are the same, but there are also differences. For example, Linux's `EAGAIN` has a value of 11, whereas the same error constant in FreeBSD and Darwin has a value 35.

Enough ranting. 😉 I'm not sure what should be done. Given limited resources (as usual), I can imagine to put in the work to define _symbolic_ constants that are uniform for all supported platforms isn't tempting, although I think that would be the "correct" thing to do. (I believe Python does this, even trying to define common symbolic constants for Posix and Windows.) _If_ someone wants to implement a mapping from numeric to symbolic errno constants and back, the source in each case should be the `errno.h` header file for each platform. For reliable APIs, it should be documented which functions are guaranteed to raise `exn:fail:filesystem:errno`.

While writing this message, I noticed that the matter is surprisingly convoluted and that the research took much more time than expected. So at some point I thought of just stopping the research and discarding the message. But maybe it's still helpful, so I finished the message. Maybe someone wants to work on this at some point.

---

<div class="post-metadata">

**Author:** ![ryanc](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/ryanc/32/71_2.png) [@ryanc](https://racket.discourse.group/u/ryanc)\
**Post date:** [April 10, 2022, 7:46pm UTC](https://racket.discourse.group/t/finer-grained-file-system-exceptions/870/6 "2022-04-10T19:46:04Z")

</div>

The [`lookup-errno`](https://docs.racket-lang.org/foreign/Miscellaneous_Support.html#%28def._%28%28quote._~23~25foreign%29._lookup-errno%29%29) function maps errno symbolic names to integers.

---

<div class="post-metadata">

**Author:** ![sschwarzer](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/sschwarzer/32/1940_2.png) [@sschwarzer](https://racket.discourse.group/u/sschwarzer)\
**Post date:** [April 10, 2022, 9:29pm UTC](https://racket.discourse.group/t/finer-grained-file-system-exceptions/870/7 "2022-04-10T21:29:32Z")

</div>

> [@ryanc](#):
>
> The [`lookup-errno`](https://docs.racket-lang.org/foreign/Miscellaneous_Support.html#%28def._%28%28quote._~23~25foreign%29._lookup-errno%29%29) function maps errno symbolic names to integers.

Awesome, thanks! 🙂

I wonder if it also maps to Windows error codes on Windows.
