Reading a text file that may have embedded nulls I've always used fgets to read a file. However, I want to read a file that may have embedded \0. I thought of using ftell to query the size, but it doesn't seem to work on all files.

I've got a test file,

31 32 33 00 34 35 36 0A E2 82 AC 0D 0A 61


Here is my fgets.

#include &lt;stdlib.h&gt; /* EXIT */
#include &lt;stdio.h&gt;  /* printf perror fputc fread */
#include &lt;string.h&gt; /* strlen */
#include &lt;errno.h&gt;  /* errno */
#include &lt;assert.h&gt; /* assert */

int main(void) {
    char file[1000], *f = file, *a;
    const int granularity = 80;
    int is_done = 0;

    for( ; ; ) {
        /* Fail when contents bigger than the size;
         would be a good spot to use realloc. */
        if(granularity &gt; sizeof file - (f - file))
            { errno = ERANGE; break; }
        if(!fgets(f, granularity, stdin))
            { if(ferror(stdin)) break; is_done = 1; break; }
        f += strlen(f);
    }

    for(a = file; a &lt; f; a++) printf("%02hhX ", *a);
    fputc('\n', stdout);
    return is_done ? EXIT_SUCCESS : (perror("stdin"), EXIT_FAILURE);
}


Running this, (I'm on a UNIX-like machine,)

$ bin/fgets &lt; test 
31 32 33 E2 82 AC 0D 0A 61 


Here is my fread.

#include &lt;stdlib.h&gt; /* EXIT */
#include &lt;stdio.h&gt;  /* printf perror fputc fread */
#include &lt;errno.h&gt;  /* errno */
#include &lt;assert.h&gt; /* assert */

int main(void) {
    char file[1000], *f = file, *a;
    const int granularity = 80;
    size_t read;
    int is_done = 0;

    for( ; ; ) {
        if(granularity &gt; sizeof file - (f - file))
            { errno = ERANGE; break; }
        read = fread(f, 1, granularity, stdin);
        if(ferror(stdin)) break;
        assert(read &gt;= 0 &amp;&amp; read &lt;= granularity);
        f += read;
        if(read != granularity) { is_done = 1; break; }
    }

    for(a = file; a &lt; f; a++) printf("%02hhX ", *a);
    fputc('\n', stdout);
    return is_done ? EXIT_SUCCESS : (perror("stdin"), EXIT_FAILURE);
}


Running this,

$ bin/fread &lt; test
31 32 33 00 34 35 36 0A E2 82 AC 0D 0A 61 


I would like to know if this is pedantically correct and how I improve.
I find your code hard to read, for the following reasons:


the variable file is not really a file but a buffer
the variable f (which usually stands for file) is a pointer into that buffer
the variable a has a name which does not convey any meaning at all
if the first if statement you indent the brace in the next line, and in the very last if statement the body is in the same line
there are spaces missing in the code at places where I expect them, such as after an if or for
the forever loop is usually written as for (;;), not for( ; ; )
the main block of the code is inside the for loop, and there's not a single empty line in that part. This suggests that the whole block is doing a single thing with no possible interruption or logical break
the comma operator is generally frowned upon
is_done is not really about the work being done, it's more about being successful


Because of all the above reasons, I would write the code differently:

#include &lt;errno.h&gt;
#include &lt;stdbool.h&gt;
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;

int main(void) {
    char buf[1000];
    size_t buflen = 0;
    const size_t granularity = 80;

    while (true) {
        if (granularity &gt; sizeof buf - buflen) {
            errno = ERANGE;
            break;
        }

        size_t nread = fread(buf + buflen, 1, granularity, stdin);
        if (ferror(stdin))
            break;

        buflen += nread;
    }

    for (size_t i = 0; i &lt; buflen; i++)
        printf("%02hhX ", buf[i]);
    fputc('\n', stdout);

    if (ferror(stdin)) {
        perror("stdin");
        return EXIT_FAILURE;
    }
}


And here is what I changed:


I renamed all variables to match their purpose
I replaced the various pointers into the buffer with indexes
I removed the is_done variable since the program should not print an error just because the file is a multiple of 80 bytes
I moved the variable a to a smaller scope by declaring it inside the for loop where it is used; I also renamed it to i, since it is now an index instead of a pointer
I replaced the comma operator with an if statement, since that is the form that is commonly used
I included &lt;stdbool.h&gt; to have a boolean type and the constants true and false
I replaced for (;;) with while (true), which is less magic
I sorted the included headers alphabetically since for headers from the C standard library, the order doesn't matter
I renamed the variable read to nread, to avoid possible conflicts with the POSIX function of the same name