6

I have some code that uses ctypes to try to determine if the file pointed to by sys.stdout is actually stdout. I know that on any POSIX-compliant system, and even on Windows, it should be safe to assume this is true if sys.stdout.fileno() == 1, so my question is not how to do this in general.

In my code (which is already using ctypes for something unrelated to my question) I carelessly had something like:

libc = ctypes.CDLL(ctypes.util.find_library('c'))
real_stdout = libc.fileno(ctypes.c_void_p.in_dll(libc, 'stdout'))
if sys.stdout.fileno() == real_stdout:
    ...

This works perfectly fine on Linux, so I didn't really think about it much. It looked nicer and more readable than hard-coding 1 as the file descriptor. But I found a few days later that my code wasn't working on OSX.

It turns outs OSX's libc doesn't export any symbol called 'stdout'. Instead its stdio.h has stdout defined as:

#define stdout __stdoutp

If I change my code to c_void_p.in_dll(libc, '__stdoutp') my code works as expected, but of course that's OSX-only. Windows, it turns out, has a similar issue (at least if using MSVC).

I will probably just change my code to use 1, but my question still stands, out of curiosity, if there's a cross-platform way to get the stdio pointer (and likewise stdin and stderr) without assuming that it's using the POSIX-compliant descriptor?

bukzor
  • 37,539
  • 11
  • 77
  • 111
Iguananaut
  • 21,810
  • 5
  • 50
  • 63
  • 2
    A "file descriptor" isn't a cross-platform concept. It's a Posix-specific. You can use `fileno(stdout)` on any Posix platform, though there's a also a predefined macro for that value named `STDOUT_FILENO`. – Kerrek SB Feb 02 '12 at 20:41
  • note that if `sys.stdout` has been replaced the original `stdout` remains in `sys.__stdout__` (unless your code changes that too). See that `sys.__stdout__.fileno()` => `1` – Dan D. Feb 02 '12 at 22:12
  • 1
    note that file decriptors (an integer "handle") is a very different thing than the stdout FILE* in C. There's no cross platform ABI for the symbols found in C libraries, so taking that path is futile. Hard coding 1 as the file descriptor for stdout would be almost infinitely more portable. – nos Feb 02 '12 at 22:43
  • `sys.__stdout__.fileno()` can't be relied on either since it can just as easily be replaced. nos understood the question, but as I suspected there's no good answer really. – Iguananaut Feb 03 '12 at 15:32

2 Answers2

4

As so often when it comes to C, if you want compatibility, you'll have to go and look in the relevant standard. Since you mention windows, I guess you're not actually wanting the POSIX standard, but rather the C one.

C99 section 7,19,1 defines stdout to be a macro, and thus not a variable. That means there's no way you can rely on finding it using dlsym (which I assume in_dll uses). The actual expression could just as well be a function call or a fixed address. Perhaps not very likely, but it is possible...

As said in the comments, the fileno function is in turn defined by POSIX, not by C. C has no concept of file descriptors. I think you're better off assuming POSIX and just checking for the value 1, which it specifies.

Per Johansson
  • 6,697
  • 27
  • 34
  • I'll go ahead and accept this as the answer, as most of what I was looking for is confirmation of what I already knew. Though I didn't know that the C standard defined it as a macro--that confirms more than anything that there's no real answer to this problem. And you're right--it would probably have been more straightforward to ask this question in terms of dlsym, which ctypes is using under the hood. – Iguananaut Feb 03 '12 at 15:36
3

If you're simply interested in making things work, rather than strict standards adherence (like me), you can find the "real" name of stdout by writing a simple C snippet:

echo -e '#include <stdio.h>\nFILE* mystdout = stdout;' > test.c
cpp test.c | tail

Gives you the output:

FILE* mystdout = __stdoutp;

This means that you also need to try ctypes.c_void_p.in_dll(libc, '__stdoutp') to cover the case of darwin.

Chris Maes
  • 35,025
  • 12
  • 111
  • 136
bukzor
  • 37,539
  • 11
  • 77
  • 111