24 comments

[ 0.65 ms ] story [ 54.5 ms ] thread
Can someone elaborate? I always treated FILE as opaque, but never imagined people could poke into it?
One issue – ISO C defines setvbuf (configure buffer mode and size of a stdio stream) but not getvbuf (get current buffer mode and size). On many platforms with non-opaque FILE, you can implement your own getvbuf by peering inside its undocumented fields.

I guess part of why it is not in the standard is that it is rarely requested functionality, but there are rare use cases where it may have value. And I think it is an unfortunate lack of orthogonality to have a setter but no corresponding getter.

stdio_ext.h offers some functionality like a "getvbuf", but not quite – e.g. __fbufsize tells you a stream's buffer size, and __flbf whether it is line-buffered – but it isn't clear how to distinguish fully buffered and unbuffered streams. And stdio_ext.h has never been standardised, it is an extension invented on Solaris and copied by Linux (and a few other platforms too, e.g. IBM z/OS).

However, who should really rely on internals of FILE? Isn't this a bad practice?
To misquote the street fighter movie: OpenBSD to Linux:

"For you the day you changed your ABI was the most important day in your life, but for me? It was Tuesday"

I enjoy the dichotomy between how bad the Linux project is at changing their ABI and how good OpenBSD is at the same task.

Where for the most part Linux just decides to live with the bad ABI forever. and if they do decide it actually needs to be changed it is a multi year drama with much crying and missteps.

I mean sure, linux has additional considerations that make breaking the ABI very scary for them. the big one is the corpus of closed source software, but being a orders of magnitude bigger project and their overall looser integration does not help any.

> bad the Linux project is at changing their ABI and how good OpenBSD is at the same task

From my perspective as a user who wants to have his programs keep working whenever the OS updates and as a programmer who does not want to waste their time playing nanny with broken dependency upgrades for previously working code (working in the sense that it did what it was supposed to do), the Linux project is actually doing the thing the right way and OpenBSD the bad way. It is basically the #1 reason i never considered using OpenBSD.

Linux' stance on not breaking backwards compatibility is exactly what i want from an OS. Now if only the userspace libraries weren't so happy to break things too...

I don't know if I agree, but this is one shining example of what makes *bsd's great, not being afraid of change. Linux should take note. So much of Windows' headaches stem from not wanting to break things, and needing to support old client code.
> So much of Windows' headaches stem from not wanting to break things

Quite acceptable for not having the headache for things breaking.

"Windows" did this 11 years ago:

>FILE Encapsulation: In previous versions, the FILE type was completely defined in <stdio.h>, so it was possible for user code to reach into a FILE and muck with its internals. We have refactored the stdio library to improve encapsulation of the library implementation details. As part of this, FILE as defined in <stdio.h> is now an opaque type and its members are inaccessible from outside of the CRT itself.

https://devblogs.microsoft.com/cppblog/c-runtime-crt-feature...

Windows has kept FILE opaque for as long as I can remember. Granted, that's not very long, only 10 or so years.
> Linux should take note

Ugh, no, it should not. As a user i prefer my existing programs to keep working whenever i update my OS and as a developer i prefer to work on new code than playing nanny with existing previously working code (working code here means the code did the task it was supposed to do) because some dependency broke itself.

In addition to "some code frobs internals", non-opaque FILE also allows for compatibility with code which puts FILE into a structure, since an opaque FILE doesn't have a size.
It happened to microsoft a while ago, they changed something in FILE and something else broke, so they went for opaque FILE.
But code outside the standard library can’t do that, can it? fopen returns a pointer to a FILE, and you can’t know how a struct FILE should be copied.

You can’t just memcpy the bits and then mix calls to fread using pointers to the old and the new FILE struct, for example. I think the standard library need not even support calls using a pointer to a FILE struct it didn’t create.

You can’t just memcpy the bits...

You certainly shouldn't, but sadly this is something which people do.

Are there any POSIX or ISO guarantees on "FILE"? I think it's safe to assume that it isn't an incomplete type, but all functions that use it operate on pointers anyway. Storing a copy of a "FILE" object might result in each copy pointing to the same underlying file handle but having different internal state.
CHERI would defend against access to internal data structures without having to bounce between address spaces, FWIW.
If you've ever done this to a C library, the first thing that you'll look at when someone else does it is not the FILE type, but how stdin, stdout, and stderr have changed.

The big breaking change is usually the historical implementation of the standard streams as addresses of elements of an array rather than as named pointers. (Plauger's example implementation had them as elements 0, 1, and 2 of a _Files[] array, for example.) It's possible to retain binary compatibility with unrecompiled code that uses the old getc/putc/feof/ferror/fclearerr/&c. macros by preserving structure layouts, but changing stdin, stdout, and stderr can make things not link.

And indeed that has happened here.

openbsd has never, ever, ever, even once maintained binary compatibility; this is just a warning about source compatibility breaking
So many words in the commit message and the announcement article, yet not a single mention of the rationale? I have a bad feeling about their practice.
The best comments always say "why" and that's missing here.

Does anyone know why this change was done? Security reasons? Preparing for future changes?

Out of curiosity. If we have this inside `bits/types/FILE.h` in Linux/GNU, does it mean the type is opaque?

  ...
  struct _IO_FILE;
  
  /* The opaque type of streams.  This is the definition used elsewhere.  */
  typedef struct _IO_FILE FILE;
  ...
Slightly side note - I donʼt understand why C++ invents new stdio based functions like std::print but rejects to import funopen() or fopencookie() which make stdio flexible similar to iostreams. As for me it should have been done ≈20 years ago.