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.