[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [MiNT] bug in unlink/MiNTLib?


On Wed, Sep 06, 2000 at 09:19:49PM +0200, Andreas Schwab wrote:
> Tomas Berndtsson <tomas@nocrew.org> writes:
> |> Assume that the folder /tmp/ exists, but /tmp/test/ does not. Also
> |> assume that the file /tmp/foo does not exist. If I then write:
> |> 
> |> rm /tmp/foo
> |> 
> |> it will return "ENOENT - No such file or directory", which is
> |> correct. However, if I write:
> |> 
> |> rm /tmp/test/foo
> |> 
> |> it will return "ENOTDIR - Not a directory"
> This is wrong.  It should return ENOENT as well.
> Andreas.

Mea culpa, will get fixed.  Tomas: If you fix that yourself in your libc,
don't fix unlink.c or remove.c but enoent.c; the same bug will probably
strike if you try to open("/tmp/test/foo", ...).

Not an excuse, but an explanation:  The error number ENOTDIR used to be
EPATH ("Path not found") in Atari/GEMDOS lingo.  The MiNTLib (ever since)
decided that the "official" errno that comes closes to EPATH is ENOTDIR.

Later on, it turned out that you cannot simply map the kernel error EPATH
to ENOTDIR, sometimes it should be ENOENT, sometimes ENOTDIR.  The result
was an internal libc function _enoent() which checks whether a kernel
EPATH should really be ENOTDIR or ENOENT, and that's where the bug sits.

There are general problems with this approach: First of all it is quite
expansive because most of the time the libc has to do exactly the same
thing that the kernel did an instance ago, it has to parse the complete
pathname, check for every pathname component whether it is a directory or
not and then set errno accordingly.  Unfortunately this is not only a
waste of resources but also unreliable.  In the situation that Tomas
describes there is a race if another process creates a regular file
"/tmp/test" before the checking in _enoent() is done.  If the regular file
"/tmp/test" exists then the correct errno for "rm /tmp/test/foo" would be
ENOTDIR.  Well, the application should actually be prepared for this
particular race but in that case the libc would have converted a correct
kernel error code into an incorrect one which is not very desirable.

When I suggested the new error codes for the kernel I actually had in mind
to make the kernel always return the correct (i. e. Unix like) error
codes.  For the sake of backwards compatibility this project was thinned
out into a mere renaming of the error codes within the kernel sources.  In
other words: For the vast majority of error conditions the kernel will
still return the same numerical values as ever, only the preprocessor
macros - the names - within the kernel sources have changed.

I would still plea for some possibility to make the kernel return correct
error codes.  I think the most promising approach for that would be
something like Pdomain(2) which should turn on full Unix
compatibility.  In this mode a process should always see standard error
codes, and the backslash should not be treated as a path
separator.  (Problem: Create a file "/tmp/back\\slash" on a VFAT partition
...).  The same would of course apply for the colon at position 2 of
filenames, i. e. no more drive letters in this mode.

Just in case somebody wonders when to expect ENOTDIR and when ENOENT:
If you try to remove/unlink a file "/foo/bar":

	ENOENT - No such file or directory
		here: Either "bar" in directory "/foo" does not exist
		or "/foo" is a dangling symbolic link (if "bar" in
		the existing directory "/foo" is a dangling symbolic link
		there would be no error).

	ENOTDIR - Not a directory
		A pathname component of "/foo/bar" is not a directory.
		here: The file "/foo" exists but it is no directory
		(and therefore "/foo/bar" cannot exist).

	EISDIR - Is a directory
		The file "/foo/bar" exists but is a directory (and
		this is the difference between unlink and remove:
		the "system call" unlink() fails with EISDIR whereas
		the libc function remove() will try a rmdir() instead.

The Linux manpage for unlink() says that EPERM (Permission denied) is
returned if the file /foo/bar "is" a directory and EISDIR is returned if
the file /foo/bar "refers to" a directory.  Looks like a pathname can only
"refer to" a directory there because I cannot convince the Linux kernel to
honor an invalid unlink request with EPERM.