Search code examples
clinuxoperating-systemsystemdup

C stdout to file using dup


I am working on a program that asks the user to input s,f or 0 as user input. S prints a predefined message to the system and f writes that predefined message to a file given by the user as an argument. 0 terminates the program.

I need to make the program only have one write statement that writes to stdout. I have to dup the predefined message printed in stdout to a file with dup2.

Now this two processes have to be seperated by input (f writes to the file, while s writes to stdout) so I am not sure how to implement it while in a switch statement.

When you input f, it shouldn't print anything to stdout.

Here is my code:

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <sys/wait.h>

#define BUFFER_SIZE 128
#define PERMS 0666

int main(int argc, char *argv[])
{
    char outBuffer[BUFFER_SIZE] = "This is a message\n";
    int count;
    int fd;
    char input =0;
    int a;
    int c;
    int k[2];
    pipe(k);
    if(argc!=2){
        printf("Provide an valid file as an argument\n");
        exit(1);
    }

    if((fd = open(argv[1],O_CREAT|O_WRONLY|O_APPEND,PERMS)) == -1)
    {
        printf("Could not open file\n");
        exit(1);
    }


        printf("0 to terminate, s to write to stdout, f to write to file\n");
        do{
        scanf(" %c", &input);
        switch(input)
        {

            case 'f':
            if((c = fork()) == 0)
            {
                close(k[0]);
                dup2(fd,1);
                close(k[1]);
             }
             else
             {
                close(k[0]);
                close(k[1]);
                wait(0);
                wait(0);
              }
              break;
            case 's':
            write(1,outBuffer,strlen(outBuffer));
           break;

            default:
            printf("Invalid Choice\n");

         }
     }while(input != '0');

     close(fd);
     return 0;
}

f right now just directs stdout to the file after pressing s(prints the outBufer message) or after triggering the default switch

my desired output:

f
f
s
This is a message
s
This is a message
f

file contains:

This is a message
This is a message
This is a message

Solution

  • Here's my take on the problem; I removed fork() and pipe() both to compile natively on Windows (gcc -g -std=c11 -Wall -c main.c && gcc main.o -o main.exe) and because I don't think they are required. When I compiled/tested with MSYS2, I had to explicitly flush stdout, or the informational message wasn't displayed until quitting.

    I refactored the switch block so that we can control the main loop with break and continue, which I rewrote as a while(1).

    #include <stdio.h>
    #include <string.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    #define BUFFER_SIZE 128
    #define PERMS 0666
    
    int main(int argc, char *argv[]) {
        char outBuffer[BUFFER_SIZE] = "This is a message\n";
        int fd, fd_f, fd_s;
        char input = 0;
    
        if(argc != 2){
            printf("Provide an valid file as an argument\n");
            exit(1);
        }
        if((fd_f = open(argv[1], O_CREAT|O_WRONLY|O_APPEND, PERMS)) == -1){
            printf("Could not open file\n");
            exit(1);
        }
    
        printf("0 to terminate, s to write to stdout, f to write to file\n");
        fflush(stdout); // messages are not printed on MSYS2 without explicitly flushing
    
        fd_s = dup(STDOUT_FILENO); // set fd_s to stdout with dup(1)
        fd = dup(STDOUT_FILENO);
        // if fd is not initialised, calling dup2 will close(0) [i.e. close(stdin)]!
        while(1) {
            scanf("%c", &input);
            fflush(stdin);
    
            if(input == '0') break;
            else if(input == 'f') dup2(fd_f, fd); // close(fd) and alias fd_f to fd
            else if(input == 's') dup2(fd_s, fd);
            else {
                printf("Invalid Choice\n");
                fflush(stdout);
                continue;
            }
            write(fd, outBuffer, strlen(outBuffer));
        }
    
        close(fd_f);
        close(fd);
        return 0;
    }
    

    Output:

    stdout

    >main.exe file.txt
    0 to terminate, s to write to stdout, f to write to file
    f
    f
    s
    This is a message
    s
    This is a message
    f
    p
    Invalid Choice
    0
    
    >
    
    # ./main.exe file.txt
    0 to terminate, s to write to stdout, f to write to file
    f
    f
    s
    This is a message
    f
    p
    Invalid Choice
    0
    
    
    

    file.txt

    This is a message
    This is a message
    This is a message