Page MenuHomeFreeBSD

stdio: rename _file to _fileno in struct __sFILE
Needs ReviewPublic

Authored by kris_tranception.com on Tue, Dec 23, 6:03 PM.
Tags
None
Referenced Files
F141926780: D54354.id168549.diff
Mon, Jan 12, 7:30 PM
F141926432: D54354.id168549.diff
Mon, Jan 12, 6:29 PM
F141919658: D54354.id168555.diff
Mon, Jan 12, 12:57 PM
Unknown Object (File)
Thu, Jan 8, 12:21 AM
Unknown Object (File)
Wed, Jan 7, 4:47 AM
Unknown Object (File)
Tue, Jan 6, 12:12 PM
Unknown Object (File)
Sun, Jan 4, 4:05 PM
Unknown Object (File)
Fri, Jan 2, 1:07 PM
Subscribers

Details

Reviewers
des
jhb
adrian
Summary

Kernel file descriptors are 32-bit integers whereas the _file field in libc stdio FILE is a 16-bit short integer.
As a result libc stdio calls such as fopen(), fdopen(), etc. fail when a process has more than 32,767 file descriptors (through whatever library or syscall opened) in use.

This change is change is in two parts:

  • D54354: rename _file to _fileno to break source code that directly attempts to access this internal libc stdio FILE field
  • D54355: add a new 32-bit _fileno file at end of struct FILE; retain old 16-bit field but renamed for binary compatibilty

Please see https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=291610#c2 for further background and related bug reports.

Test Plan

Have locally rebuilt and installed 16-CURRENT kernel & world, as well as git and emacs-nox ports, which include dependencies perl and gnulib (part of m4). No issues noted, in particular the perl issues reported in bug #203900 ten years ago were not observed (perl now uses fdclose() instead of overwriting _file). gnulib as well as /bin/cat in the base system both access internal FILE members other than _file directly but appeared to work correctly.

The following simple test program below also worked as expected (failed on current stdio, succeeded on patched stdio, binary compiled against current stdio but run on patched stdio succeeded with fileno() reporting the actual file descriptor but fileno_unlocked() (which is implemented as a macro) reporting -1).

#include<stdio.h>
#include<sysexits.h>
#include<err.h>
#include<unistd.h>
#include<sys/socket.h>
int main() {
  int f;
  for (int i=0; i<0x8000; i++)
    if ((f=socket(PF_INET, SOCK_STREAM, 0))<0)
      err(EX_OSFILE, "socket");
  if (close(f)<0)
    err(EX_OSFILE, "close");
  FILE* const fp = fopen("/dev/null", "r");
  if (!fp) err(EX_OSFILE, "fopen");
  printf("fileno         : %d\n", fileno(fp));
  printf("fileno_unlocked: %d\n", fileno_unlocked(fp));
  if (fclose(fp) == EOF)
    err(EX_OSFILE, "fclose");
  return 0;
}

Question: should /usr/src/lib/libc/tests/stdio/fopen_test.c and friends have a test case for >32,767 currently open sockets? Note this could fail for other reasons such as low resource limits on the build machine, sysctl set low, etc.

All ports should be built against this to see what other ports might break.

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Passed
Unit
No Test Coverage
Build Status
Buildable 69460
Build 66343: arc lint + arc unit

Event Timeline

Remove inadvertent edit to comment (i.e. read_only_fileno should have remained file_only_file)

kevans added a subscriber: kevans.

+des for recent stdio work, jhb for having worked on something like this specifically that hadn't landed