How to handle POSIX errors

Hi.

I’m writing a foreign C function for the realpath(3) system library call. I want to do it right, so I try to handle any errors correctly.

realpath(3) returns errors via the errno variable. For ENOENT (file not found) errors, I’ve found the existence_error() exception in the description of absolute_file_name. It doesn’t seem to be documented, though, what existence_error(A,B) means. Anyway, I’m throwing the same as what absolute_file_name throws. (But what is a “source_sink”?)

But there are a couple more errors, which I want to handle correctly too, such as EACCES and ENOTDIR. How should those be handled? Are there more standardised exceptions? Is there an exception, which carries an errno value? Where are the errors and exceptions documented?

I plan to post the results here, when I’m finished.

Cheers,
Volker

Note that this is more or less doing what absolute_file_name/2,3 is doing. The main difference is that this predicate doesn’t work up to the root, but up to a table of previously stored canonical directories and does some seeding in this, notably to accept $HOME as a canonical dir, regardless of whether it in some symlinked position or not.

As for errors, internally there is PL_error() that has an ERR_ERRNO, errno argument option that will try to map some of the POSIX errors to related Prolog errors. This isn’t easy though and thus mainly used for the general errors such as ENOMEM. For example for EPERM you need the object acted upon and the desired action. Many of the POSIX errors have no obvious Prolog equivalent (EINTR, EINVAL, …) As a result, most of the internal code handles the scenarios that dictate a clean Prolog error special and use the above for any other POSIX error, resulting in some pretty general error that more or less states something went wrong at the OS level :frowning: If you want to investigate this and come up with a good API to improve on this, please contribute!

I need to expand the symlinks in some paths. I’ve tried, and absolute_file_name/3 doesn’t do that. realpath is the standard function for this (at least in the non-Prolog world). So I thought I’d add realpath/2 as a foreign predicate.

I found it in the SWI-Prolog sources in src/pl-error.c, but there is no ERR_ERRNO mentioned anywhere in that file.

And since it’s interal to SWI-Prolog, how should you use it? Do you mean to copy it out of pl-errno.c and include it in the source code of the realpath/2, which I’m trying to write?

I don’t quite get it. What is a “Prolog error special”?

What kind of API do you have in mind? A function which replaces PL_error() and is included in the Foreign Language Interface?

Cheers,
Volker

Maybe read_link/3?

Sorry. There is MSG_ERRNO that causes the message comment to be set to the strerror() output. A grep through the source will tell you how that is used. It is far from ideal …

Typo :frowning:

I guess ideally we have something like

PL_errno_error(int errno, ...)

The problem is the …, That should give enough info such that for e.g. open() we can map EPERM to permission_error(open, source_sink, File), i.e. we must pass the operation and the think the operation works on. Different POSIX errors require different additional information. Possible there is a quite small set of additional information that allows generating the correct Prolog errors. Most of the OS errors are some sort of existence, resource, representation or permission error.

This is the documentation for read_link/3:

“If File points to a symbolic link, unify Link with the value of the link and Target to the file the link is pointing to. Target points to a file, directory or non-existing entry in the file system, but never to a link. Fails if File is not a link. Fails always on systems that do not support symbolic links.”

I find it’s not so clear. Does “but never to a link” mean “but never to a symlink”?

Does, what is done with Target mean, that any symlinks are followed, until the symlink target no longer is a symlink?

read_link/3 doesn’t expand symlinks in the beginning or middle of a path. It only reads (Link argument) and expands (Target argument), symlinks in the last path component. What I need is exactly readlink(3).

Thanks tough,
Volker

If is a simple loop that, as long as the path points at the symlink, reads the link and determines the new target (notably if the link is relative). So, in the end you have the location the link points at. It is intended to perform safe atomic updates of a file’s content and ensure the real file is updated rather than a link being replaced by a file. Thus, you deference the link, create a new file in the same directory and when complete rename the new file to the old one.

The additional arguments (the “…”) could be made use of, like in PL_unify_term. A marker, for instance EI_FILENAME (“EI” for “Error Information”), could be used, with the value following. An end marker (EI_END) would end the list of pieces of error information.

Such as this:

PL_errno_error(errno, EI_HANDLE, my_file_handle, EI_FILENAME, my_filename, EI_END);

The PL_errno_errorr function would only use the error information pieces which it needs, depending on the errno value. So it simply can be called when a POSIX error occured, and it isn’t necessary make the call dependent on the errno, providing different pieces of information for each case.

Is there any documentation, about the error values (such as existence_error() and permission_error()) that SWI-Prolog uses? And still: what is a “source_sink”?

That’s what I guessed. Could you please write it into the documentation of read_link/3?

EI_* could be an option. Another option might be a struct, so you do

error_info ei = {.type = "source_sink", .name = name, .op = "open"};
PL_errno_error(errno, &ei);

The basics are the ISO errors. There are a few generic extensions. You find these in library(error). Next there are a lot of specific errors. One source is boot/messages.pl and the other src/pl-error.c

The great ISO term for a file :slight_smile: Well, I think the authors meant something more general, but in practice it is mostly that. I general I’d say things on which open/4 works.

I have some hope people will send PRs for these things …

I didn’t know that syntax (“.type = …”). I tried it and it seems like non-mentioned members are set to null. Is that the case? If so, I think I like your approach better, since it is type safe.

PR = pull request ?

Bye,
Volker

Yes. AFAIK, if you just declare a struct var in C it is not initialised, but if you initialise one field it sets the rest of the struct to 0. That is why you can normally use struct mystruct myvar = {0} to get a zero-initialized struct regardless of the fields.

Yip. Anything else works as well, but pull requests are pretty efficient to deal with on both ends when you get used to the approach.

Hi!

I don’t understand what you have in mind. Should I hack the SWI-Prolog source code?

I don’t know why PL_error() isn’t made available as a part of the Foreign Language Interface. Or do you have in mind to let me add a frontend to PL_error()?

I still don’t know what you mean by “Prolog error special”.

Cheers,
Volker

Eventually, if you want to contribute a better way of reporting OS errors, this is what needs to be done :slight_smile:

It was intended for that. However, the interface is not type-safe and develops too much with changes to the internals, so making it public is asking for trouble.

It appears that most people consider throwing exceptions too hard. That is why I added PL_type_error(), etc. as well as the automatic exceptions from e.g. PL_get_atom_ex(). Extending this with something that is easy to raise a fair error from an arbitrary system call would be nice to have. Once we have that we probably want to generalize it. Many libraries come with an errno/strerror() style error API. It would be great if you could supply a table (as an array of C structs) that provide a mapping from the error codes to Prolog errors. Right now I’m typically lazy and map such errors to e.g. odbc_error(Code, Message).

Hi!

I have a first draft of how such a table could look like, and how it would be used. But Discourse seems to not allow appending a file to a post.

Perhaps we should discuss the rest by private mail. There are some points which need to be clarified.

Cheers,
Volker

Discourse restricts file uploads based on file type.

Currently

What was the file type you used?

It was “.c”.

Volker

Thanks for taking the time to look at this. Continue as you seem fit, either private mail, github or when .c is allowed on this forum.

@EricGT: I see little reason to reject extensions from programming languages that are used here.

@volker-wysk @jan

Updated for file type of c. Let me know if it does not work.
If other file types are needed for programming languages just let us know.
If you ask for something like the zip file type then the admins would need to discuss it (think security concerns).

Thanks.