Search code examples
clinuxfileunixfork

C writing into files ignores new line?


Given the following code, I have: printf("hello\n"); so I am expecting to see hello\n in myfile. but when I run my program I see haha which means \n was ignored, why is that?

Worth Noting: when I replace printf("hello\n"); with printf("hellos"); I don't see the s letter being printed as well. So I think maybe something is writing on top of it but the question is who and why?

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/file.h>
#include <string.h>


int main() {
// creates a new file having full read/write permissions
    int fd = open("myfile", O_RDWR | O_CREAT, 0666);
    write(fd, "haha\n", 5);
    close(fd); // line 6
    fd = open("myfile", O_RDWR); // line 7
    close(0);
    close(1);
    dup(fd);
    dup(fd);
    if (fork() == 0) {
        char s[100];
        dup(fd);
        scanf("%s", s);
        printf("hello\n");
        write(2, s, strlen(s)); // line 18
        return 0; // line 19
    }
    wait(NULL);
    printf("Father finished\n");
    close(fd);
    return 0;
}

contents of myfile after running:

haha
helloFather finished (new line after this)

Solution

  • First of all, the behavior is undefined. You are starting to use stdout that refers to the same file descriptor as stdin without closing or flushing stdin before doing it. Let's try to take the important stuff from POSIX 2.5.1 Interaction of File Descriptors and Standard I/O Streams:

    [...] if two or more handles are used, and any one of them is a stream, the application shall ensure that their actions are coordinated as described below. If this is not done, the result is undefined.

    [...]

    For a handle to become the active handle, the application shall ensure that the actions below are performed between the last use of the handle (the current active handle) and the first use of the second handle (the future active handle). [...]

    [...]

    For the first handle, the first applicable condition below applies. [...]

    • [...]

    • If the stream is open with a mode that allows reading and the underlying open file description refers to a device that is capable of seeking, the application shall either perform an fflush(), or the stream shall be closed.

    Your code does:

        scanf("%s", s); // associates stdin with fd
        // Ups - no flush(stdin) nor fclose(stdin)
        printf("hello\n");  // associates stdout with fd - undefined behavior
    

    The result you are seeing comes from that scanf calls ungetc which increments file position but also "remembers" to un-increment file position once the stream is flushed. Because it is flushed when child terminates, the file position is decremented, and parent overwrites last character of the child.