Index | Thread | Search

From:
"Theo de Raadt" <deraadt@openbsd.org>
Subject:
Re: openat(2) is mostly useless, sadly
To:
Mark Kettenis <mark.kettenis@xs4all.nl>
Cc:
h@hartzer.sh, tech@openbsd.org
Date:
Sat, 31 May 2025 09:04:51 -0600

Download raw body.

Thread
Mark Kettenis <mark.kettenis@xs4all.nl> wrote:

> > From: "Theo de Raadt" <deraadt@openbsd.org>
> > Date: Fri, 30 May 2025 19:17:46 -0600
> > 
> > Steffen Nurpmeso <steffen@sdaoden.eu> wrote:
> > 
> > > I would not use ENOTTY for F_BELOW on !DIR, maybe ENOTDIR is
> > > better.
> > 
> > >From errno(2) manpage:
> > 
> >      20 ENOTDIR Not a directory. A component of the specified pathname
> >              existed, but it was not a directory, when a directory was
> >              expected.
> > 
> > F_BELOW is fcntl(2).
> > 
> >      int
> >      fcntl(int fd, int cmd, ...);
> > 
> > But there is no pathname.  Yes it is partly vague, but then grep the whole
> > tree for ENOTDIR and always anticipates a pathname at a higher level.
> 
> Note that openat(2) says:
> 
> [ENOTDIR]          The path argument specifies a relative path and the fd
>                    argument is a valid file descriptor but it does not
> 		   reference a directory.
> 
> So here ENOTDIR does apply to a file descriptor.

That is in the openat(2) page, where there is a pathname.

But fcntl() has no pathname.

However, it looks like this has been used for non-pathname circumstances before:

int
sys_fchdir(struct proc *p, void *v, register_t *retval)
...
        if (fp->f_type != DTYPE_VNODE || vp->v_type != VDIR) {
                FRELE(fp, p);
                return (ENOTDIR);
        }

And fchdir(2) says:

     [ENOTDIR]          The file descriptor does not reference a directory.

Checking other manual pages in a sloppy way,

grep 'file desc' * | grep 'directory'
access.2:the directory associated with file descriptor
access.2:argument is a valid file descriptor but it does not reference a directory.
chdir.2:The file descriptor does not reference a directory.
chmod.2:argument is a valid file descriptor but it does not reference a directory.
chown.2:the directory associated with file descriptor
chown.2:argument is a valid file descriptor but it does not reference a directory.
chroot.2:Passing directory file descriptors via
link.2:is a valid file descriptor but it does not reference a directory.
mkdir.2:the directory associated with file descriptor
mkdir.2:argument is a valid file descriptor but it does not reference a directory.
mkfifo.2:the directory associated with file descriptor
mkfifo.2:argument is a valid file descriptor but it does not reference a directory.
mknod.2:the directory associated with file descriptor
mknod.2:argument is a valid file descriptor but it does not reference a directory.
open.2:the directory associated with file descriptor
open.2:argument is a valid file descriptor but it does not reference a directory.
pathconf.2:argument is a valid file descriptor but it does not reference a directory.
readlink.2:the directory associated with file descriptor
readlink.2:argument is a valid file descriptor but it does not reference a directory.
rename.2:is a valid file descriptor but it does not reference a directory.
stat.2:the directory associated with file descriptor
stat.2:argument is a valid file descriptor but it does not reference a directory.
symlink.2:the directory associated with file descriptor
symlink.2:argument is a valid file descriptor but it does not reference a directory.
unlink.2:the directory associated with file descriptor
unlink.2:argument is a valid file descriptor but it does not reference a directory.
utimes.2:the directory associated with file descriptor
utimes.2:argument is a valid file descriptor but it does not reference a directory.

Some of these follow the form

     [ENOTDIR]          The path argument specifies a relative path and the fd
                        argument is a valid file descriptor but it does not
                        reference a directory.

I think this would be a good diff to intro(2) to explain the situation that
we had a few ENOTDIR related to non-pathname conditions, but the openat()
family of calls has greatly expanded the situation.

Index: intro.2
===================================================================
RCS file: /cvs/src/lib/libc/sys/intro.2,v
diff -u -p -u -r1.73 intro.2
--- intro.2     19 Dec 2022 18:13:50 -0000      1.73
+++ intro.2     31 May 2025 15:03:24 -0000
@@ -159,8 +159,9 @@ A hard link to a file on another file sy
 An attempt was made to apply an inappropriate function to a device,
 for example, trying to read a write-only device such as a printer.
 .It Er 20 ENOTDIR Em "Not a directory" .
-A component of the specified pathname existed, but it was
+A file descriptor or a component of the specified pathname existed, but it was
 not a directory, when a directory was expected.
+.Pp
 .It Er 21 EISDIR Em "Is a directory" .
 An attempt was made to open a directory with write mode specified.
 .It Er 22 EINVAL Em "Invalid argument" .

Then, we can use ENOTDIR for a condition in fcntl(2).