From: Teemu Likonen on
I'm wondering what would be the best way to detect the cause of some
file errors. I mean opening a file may fail because the file doesn't
exist or user has no access to the file (for example). I'm looking for
ideas to detect the difference. The condition type doesn't tell much:

(handler-case
(with-open-file (s "/etc/shadow")) ;No permissions to read
(t (c) (princ-to-string (type-of c))))
=> "SIMPLE-FILE-ERROR"

(handler-case
(with-open-file (s "/does-not-exist"))
(t (c) (princ-to-string (type-of c))))
=> "SIMPLE-FILE-ERROR"

CL implementation may provide error messages through a PRINT-OBJECT
method. This is how SBCL would say with (princ-to-string c):

error opening #P"/etc/shadow": Permission denied
error opening #P"/does-not-exist": No such file or directory

But what if I want to handle these errors differently and perhaps print
a different error messages for user? Does the condition object contain a
slot with standardized error code or something like that to see what's
the actual cause of the file error?

Or do I just have to detect the difference myself with something like
this:

(define-condition file-does-not-exist (file-error) nil)
(define-condition file-is-not-readable (file-error) nil)

[...]

(unless (file-exists-p file)
(error 'file-does-not-exist :pathname file))

(handler-case
(with-open-file (s file :direction :input)
...)
(file-error (c)
;; Probably no read access
(error 'file-is-not-readable :pathname (file-error-pathname c))))

So now the different types of file errors would be signalled as
FILE-DOES-NOT-EXIST and FILE-IS-NOT-READABLE which some other handler
code can handle.

What are your suggestions?
From: Zach Beane on
Teemu Likonen <tlikonen(a)iki.fi> writes:

> I'm wondering what would be the best way to detect the cause of some
> file errors. I mean opening a file may fail because the file doesn't
> exist or user has no access to the file (for example). I'm looking for
> ideas to detect the difference.

One way is via :if-does-not-exist, e.g.

(open "/etc/shadow-blah-blah" :if-does-not-exist nil) => nil

(open "/etc/shadow" :if-does-not-exist nil) => signals error

Zach
From: Pascal J. Bourguignon on
Teemu Likonen <tlikonen(a)iki.fi> writes:

> I'm wondering what would be the best way to detect the cause of some
> file errors. I mean opening a file may fail because the file doesn't
> exist or user has no access to the file (for example). I'm looking for
> ideas to detect the difference. The condition type doesn't tell much:
>
> (handler-case
> (with-open-file (s "/etc/shadow")) ;No permissions to read
> (t (c) (princ-to-string (type-of c))))
> => "SIMPLE-FILE-ERROR"
>
> (handler-case
> (with-open-file (s "/does-not-exist"))
> (t (c) (princ-to-string (type-of c))))
> => "SIMPLE-FILE-ERROR"
>
> CL implementation may provide error messages through a PRINT-OBJECT
> method. This is how SBCL would say with (princ-to-string c):
>
> error opening #P"/etc/shadow": Permission denied
> error opening #P"/does-not-exist": No such file or directory
>
> But what if I want to handle these errors differently and perhaps print
> a different error messages for user? Does the condition object contain a
> slot with standardized error code or something like that to see what's
> the actual cause of the file error?

Unfortunately, conditions are somewhat underspecified in Common Lisp.
To the point they're almost useless, for automatic processing or
recovery.

When you're writing your own code, of course, you can specify a rich
condition hiearchy, which would allow you to handle all exceptionnal
cases without any difficulty. But then, in an OO program you could
very well have 3 to 10 times more condition subclasses than program
classes!


> Or do I just have to detect the difference myself with something like
> this:
>
> (define-condition file-does-not-exist (file-error) nil)
> (define-condition file-is-not-readable (file-error) nil)

See, already for one class (STREAM), and one operation (OPEN) you
defined two conditions. To really use conditions, the Common Lisp
standard would have to define thousands of conditions. Of course, an
implementation would not signal a given condition if it never occured,
but if the situation defined for a given condition occured, the
standard would mandate the implementations to signal the corresponding
condition.

In any case, my point here is that you have the sources of the
implementations. Define the thousands of condition subclasses needed
(the "Exceptionnal Situations Ontology"), and implement them in each
free implementation.


> What are your suggestions?

You could try indeed to PRINC the condition, and use regexps to try to
identify the specific situation, and from this, signal the refined
conditions you defined. (I did that once). Obviously, implementation
specific (you can write it as a portability layer). The pain here is
that it's very version specific, sometimes even user environment
specific (in clisp, the error messages are localized). Since you're
already doing something implementation dependent, why not further
inspect the actual conditions signaled by the implementation. For
example, in the case of clisp, most bare Common Lisp conditions are
subclassed with a SIMPLE- version, which has a format control string
and arguments. Using (unexported) readers from the SYSTEM package,
you can get the list of arguments and usually find there, useful
processable details about the condition. But not all implementations
are so "helpful".

--
__Pascal Bourguignon__ http://www.informatimago.com/
From: Teemu Likonen on
* 2010-07-01 16:38 (+0200), Pascal J. Bourguignon wrote:

> Unfortunately, conditions are somewhat underspecified in Common Lisp.

OK. Fortunately, for me it's usually enough to detect the difference
between non-existing file and other file errors. Still, for possible
future needs I'll probably write some helper functions for checking
file's type (regular file, directory, socket etc.), owner, group and
permissions through POSIX stat.

> You could try indeed to PRINC the condition, and use regexps to try to
> identify the specific situation, and from this, signal the refined
> conditions you defined.

I think nobody wants to go with this kind of kludges. :-)
From: Teemu Likonen on
* 2010-07-01 14:15 (+0300), Teemu Likonen wrote:

> (define-condition file-is-not-readable (file-error) nil)

> (handler-case
> (with-open-file (s file :direction :input)
> ...)
> (file-error (c)
> ;; Probably no read access
> (error 'file-is-not-readable :pathname (file-error-pathname c))))

I just found out that SBCL has SB-POSIX:ACCESS function which makes it
easy to check if user has permissions to some file or directory. Errors
are signalled as condition SB-POSIX:SYSCALL-ERROR which has slot ERRNO
containing a standard POSIX error code. So:


(handler-case (sb-posix:access "/etc/shadow" #b100) ;read access
(sb-posix:syscall-error (c)
(case (sb-posix:syscall-errno c)
(2 "File not found")
(13 "No permissions")
(36 "Filename too long"))))

=> "No permissions"


There are predefined variables for error codes: SB-POSIX:ENOENT (2),
SB-POSIX:EACCES (13), SB-POSIX:ENAMETOOLONG (36) etc.