3

I've written this small program in C, which intends to read stdin line-by-line and finally echo each line back to stdout. It is designed to stop reading input when either EOF is detected or a blank line is found. It appears to work alright when reading input from the keyboard, but when I pipe the output of a Windows console command (DIR, SORT and TYPE were all tried), it fails with a "Broken pipe". Why?

Here's the code:

#include <stdio.h> #include <stdlib.h> #define BUFLEN 80 int main(int argc, char *argv[]) { char *s; FILE *fp; fp = fopen("tmpstdin.txt", "w+"); if (fp == NULL) { fputs("File I/O error: Cannot create file.\n", stderr); exit(-1); } s = calloc(BUFLEN, sizeof (*s)); if (s == NULL) { fputs("Memory allocation error.\n", stderr); exit(-1); } while (fgets(s, BUFLEN, stdin) != NULL && *s != '\n') if (fputs(s, fp) == EOF) break; if (ferror(stdin) != 0) { fputs("I/O error on stdin.\n", stderr); exit(-1); } if (ferror(fp) != 0) { fputs("File I/O error: Cannot write to file.\n", stderr); exit(-1); } rewind(fp); while (fgets(s, BUFLEN, fp) != NULL) fputs(s, stdout); if (ferror(fp)) { fputs("File I/O error: Cannot read from file.\n", stderr); exit(-1); } fclose("tmpstdin.txt"); return 0; } 

Here's the command line used under Windows cmd.exe (assume the above code was compiled to foo.exe):

dir /B | foo.exe 
20
  • Generally speaking, you should check ferror() only when the return value of a function call suggests that there might be something to find, and it's important to distinguish between errors and end-of-file. Although it's not wrong to check at other points, it does needlessly complicate your code. Commented Aug 27 at 19:24
  • @dbush: The condition of the first while loop has fgets() reading from stdin; the body of the loop has it writing to tmpstdin.txt. Perhaps the code is unclear or am I missing something? Commented Aug 27 at 19:25
  • 1
    Whoever told you that you needed to process strings entered from the keyboard in such a way either didn't know what they were talking about or was lying to you. Perhaps lying benevolently, to point you in a direction that they thought would be productive. Any way around, it's not true. Commented Aug 27 at 20:34
  • 1
    Anyway, the issue must have something to do with your compiler and / or execution environment. I do not reproduce it in my environment, and I don't see any reason to think I would. Do note the genuine error described in your answer (as I see you have done), but even that would not explain the behavior you describe. Commented Aug 27 at 20:49
  • 1
    Note that on error, fgets() should not only return a null pointer, but also set errno appropriately. It would be appropriate and possibly revealing to use perror() to get a more meaningful diagnostic out of that than the generic ones you are now outputting. Commented Aug 27 at 20:56

1 Answer 1

2

The code works for me after I made one small update. Please note the 2nd-to-last line of
code. The original code gave the file name to fclose() as a parameter. But fclose() wants the file pointer that was given by fopen().

I provided this command line to the executable per your post:

dir /B | foo.exe 

The result is a file named tmpstdin.txt which has a nice directory list. Code tested by me just now. Was the improper use of fclose() the root cause of your issue? Not sure. I just know that once I corrected the code error, compiled and ran the executable, the program worked as expected. Best wishes . . .

#include <stdio.h> #include <stdlib.h> #define BUFLEN 80 int main(int argc, char *argv[]) { char *s; FILE *fp; fp = fopen("tmpstdin.txt", "w+"); if (fp == NULL) { fputs("File I/O error: Cannot create file.\n", stderr); exit(-1); } s = calloc(BUFLEN, sizeof (*s)); if (s == NULL) { fputs("Memory allocation error.\n", stderr); exit(-1); } while (fgets(s, BUFLEN, stdin) != NULL && *s != '\n') if (fputs(s, fp) == EOF) break; if (ferror(stdin) != 0) { fputs("I/O error on stdin.\n", stderr); exit(-1); } if (ferror(fp) != 0) { fputs("File I/O error: Cannot write to file.\n", stderr); exit(-1); } rewind(fp); while (fgets(s, BUFLEN, fp) != NULL) fputs(s, stdout); if (ferror(fp)) { fputs("File I/O error: Cannot read from file.\n", stderr); exit(-1); } fclose(fp); /* <-- Please note correction */ return 0; } 
Sign up to request clarification or add additional context in comments.

20 Comments

That's definitely a valid correction, but the OP claims that they get errors before their bad fclose() call.
The OP doesn't show how he executes the program. The error he claims is being issued is related to pipes, and he is using no pipes here. Probably he has redirected the output of another command to his program. Bu as he doesn't show it...
The question does show how he executes the program. An example is presented at the end, demonstrating piping the output of dir /B into his program.
No John, he doesn't. I run his program under linux, and Windows (building with plain Microsoft XCode compiler) and apart from the compiler flags telling me that fopen is a dangerous call and suggesting me to use fopen_s the result was ok running it in a cmd.exe console but showed failures under PowerShell.exe. So it is not clear how he run the command :) It's not clear to me how Microsoft impelements his fopen() routine, nor it is clear why they mark it as dangerous. But it is clear to me that the way the OP has launched the application is not clear.
cmd.exe which predates from the time of MS-DOS, executes a pipe by running the programs of the pipe in sequence (not simultaneously as it is done in POSIX) using temporary files to hold the data between programs. No pipes (in the sense of UNIX) are available in Windows, so it is not clear how the OP starts the pipe :)
@gregspears I've now fixed the error (thanks for pointing it out) but the program still fails for me when taking piped input. In fact, it does so at the first while loop, where both file streams (stdin and fp) are tagged by ferror(). I find this very unusual, considering it works with piping at a Linux command prompt. Are you on Windows? If so, which compiler are you using?
Hi- understood. Compiled MS Visual Studio; op ck done in Windows 10 V 22H2. To all: I knew my find & fix might not have related to root cause for OP -- but since I did find a code error and it worked after repair during op ck . . . well, you know where I'm going with this. 😁 Thanks for your kind understanding.
How do you pipe input to that command? Can you please explain how do you pipe command output to this program? Probably the error is in there....
Your program (including your correction or not) works (not perfectly without the correction) on Linux and FreeBSD (in FreeBSD, you get a SIGBUS and a core file, but the output file shows created and the output is shown on screen before the trap, In linux you get a SIGSEGV). You must know that pipes (the way UNIX implements them) are a specific feature of POSIX systems, and Windows is not such. You need probably to use some Windows product to use POSIX features (E.g. Cygwin) but probably, pipes, the way Windows implements them, is not portable enough to make your system work.
Here my two cents: If you run (I've done so) the program from cmd.exe it will run (it runs to me, at least) but if you run it through PowerShell.exe it fails on a bad syntax error. How do you run the program?
The problem is probably that windows doesn't implement pipes at the kernel side, but pipes commands by using an automatic temporary file (this is legacy of MS-DOS trying to emulate unix pipes) so no pipes at all. You use a temporary file that is used to dump output of the first command, then you run the second taking input from the file. Powershell doesn't implement piping syntax (or at least doesn't do it using the | syntax. Please edit your question telling us how do you run the program (not the command line you use, but the shell you use to run it)
Hi Luis. You didn't mention on which platform you use PowerShell, but from what I've read it fully supports piping. It is possible to pipe the output of "Get-ChildItem -Name" to the executable? I'll say that this thread is somewhat outdated now that I've worked out a way to get rid of the temporary file based on suggestions by JohnBollinger.
Get-ChildItem -Name | foo.exe
...Piping into whatever you named the executable. Does it work in PowerShell? I don't have it on any machine near me, so I can't try it myself.
Get-ChildItem -Name | .\foo.exe
I've just tested the above with PowerShell on Windows and it worked. What may be causing your problem is that PowerShell, by default, doesn't search your current directory for executables (unlike cmd.exe and its predecessor, COMMAND.COM). So if you compiled the above code to a directory not listed in your $Env:Path variable (PowerShell's equivalent of PATH in both cmd.exe and COMMAND.COM) it will not find your newly made binary. Try this at a PowerShell prompt (assuming your compiler output a binary named foo.exe to your current directory): Get-ChildItem -Name | .\foo.exe
This sounds like a potential line-endings problem. Make sure all of your uses of stdio functions are using binary mode.
I've built my example using both the Microsoft Visual C++ 2010 and Open Watcom 1.1 compilers. Executables generated by both accept pipes without a peep. Looks like a specific compiler issue, given Borland's is the only one making problematic binaries. Thanks to all who took the time to look and to reply. I'll hold off closing this thread for a few days, in case someone has other ideas.
Use gcc in Cygwin and run it on a Cygwin console. Unix pipes are not a windows feature.
Don’t return negative numbers from your program. Return values in the range 0..126, inclusive. 1 is the usual choice, which in C programs you can get just by using the EXIT_FAILURE macro.