On 10/23/26 18:18, Andrea Venturoli wrote:

I modified the code like this:
    /* we reserve fds for children to communicate with us, so they
       better be available. */
    for (fd = STATUS_FD; fd <= LISTEN_FD; fd++) {
      syslog(LOG_CRIT,"Resetting fd %d (previously %m)",fd);
      int closeret=close(fd);
      syslog(LOG_CRIT,"Tried closing: ret=%d, %m",closeret);
      int newfd=dup(0);
      if (newfd != fd) fatalf(2, "couldn't dup fd 0: %m (newfd=%d)",newfd);
    }

Now I get:
Oct 23 15:42:21 test master[34885]: Resetting fd 3 (previously No error: 0) Oct 23 15:42:21 test master[34885]: Tried closing: ret=-1, Bad file descriptor Oct 23 15:42:21 test master[34885]: couldn't dup fd 0: Bad file descriptor (newfd=5)
Oct 23 15:42:21 test master[34885]: exiting
Oct 23 15:42:21 test root[34889]: /etc/rc: WARNING: failed to start cyrus_imapd

So:
_ close on fd 3 (STATUS_FD) fails; errno shows it's because there's no fd 3. Fine. _ dup actually works, but doesn't return 3; instead it returns 5! (It's 4 in your report, but the important thing is it's not 3).

This is strange, since dup(2) clearly states:
The new descriptor returned by the call is the lowest
numbered descriptor currently not in use by the process.
...
Still investigating...
Hello.
I thinks it's worth moving this threads to hackers...



I tried following this in the debugger (on 15.1).

The first file the "master" process opens is /etc/libmap.conf.
I went through sys_open and I think something is strange. On entering fdalloc:
fdp->fd_freefile=4
fdp->fd_map=0x7

IIUC correctly, this means fds 0, 1 and 2 are used, but the next "free" one is 4! 3 seems to have been skipped.

In fact, returning from fdalloc:
fdp->fd_freefile=5
fdp->fd_map=0x17

Stilll 3 is missing, but the next is 5.



Later, when the file (fd 4) is closed via sys_close, fdunused is called:
        fdp->fd_map[NDSLOT(fd)] &= ~NDBIT(fd);
        if (fd < fdp->fd_freefile)
                fdp->fd_freefile = fd;

This functions brings everything back to:
fdp->fd_freefile=4
fdp->fd_map=0x7

The code is fine, assuming either:
_ there is a lower free fd (so fdp->fd_freefile should not change);
_ this (4) becomes the new lowest free fd.

IOW it does not do its job, since there was the "hole" (fd 3) earlier.



This goes on for other files, but the point is that the code everywhere assumes that fd_freefile points to the right first free FD.
If this assumption is broken it never gets fixed again.

I guess the real question is why this incoherence appears in the first place, but I don't have an answer yet.


 bye
        av.

P.S.
I think this also explains why your workaround (duplicating stdin to file descriptor 3) works:
int
kern_close(struct thread *td, int fd)
{
        struct filedesc *fdp;
        struct file *fp;

        fdp = td->td_proc->p_fd;

        FILEDESC_XLOCK(fdp);
        if ((fp = fget_noref(fdp, fd)) == NULL) {
                FILEDESC_XUNLOCK(fdp);
                return (EBADF);
        }
        fdfree(fdp, fd);

        /* closefp() drops the FILEDESC lock for us. */
        return (closefp(fdp, fd, fp, td, true, true));
}

kern_close won't call fdfree if FD 3 doesn't exist.
Again, this is correct, assuming the fdp table is coherent.
So a possible workaround (general, not limited to Cyrus startup) could be to call fdfree anyway here.
In any case it would still not address the root cause.

Reply via email to