Reverse all lines in a file
Let's get the broad picture right first.


Do you know Schlemiel the Painter? No? Well, read about him on Joel's blog.
Don't follow in his footsteps; they are too deep because he always restarts from the beginning.
You might want to take that opportunity to make your code tolerate zero-bytes.


Now, having gotten that off the agenda, some other gross wastefulness: you are allocating memory on the order of twice the files size. But you could easily avoid that: Only read in a line, transform it, write it to the output.

There you have two basic good choices:


Modify the file in-place, as your transformation does not affect how much space each line needs. You don't even need any additional disk-space.
Write the output to a temporary file, and then atomically update it by moving: rewrite existing file so that it gets replaced by new version atomically, only once fully written


Now, about reducing the tedium of repeating the exact same code. That's one of the (few) places where the C preprocessor shines:

#define TRY(cond) if(cond) {} else goto error;
// Use it
#undef TRY




Now lots of other small things:


Curiously, one normally doesn't extract a functions precondition-checking into another function, as then the precondition is no longer there to see, and anyway the boilerplate is normally longer than what was extracted: main → validate_cmdline_args.
Why do you use strchr to find the end of a string, instead of the better-known (and probably more efficient) strlen?
For completeness, casting the second argument, a character-literal, to int is completely useless because it's already an int.
There's no reason to define tmp in reverse_string outside the loop. Actually, wouldn't that be a good place for a for-loop?

void reverse_string(char* str) {
    for(char* end = str + strlen(str) - 1; str &gt; end; ++str, --end) {
        char tmp = *str;
        *str = *end;
        *end = tmp;
    }
}

Strictly speaking, both your original and my replacement for reverse_string are UB if strlen(str) == 0, because that would create a pointer one before the string. That can be fixed by replacing the first 1 with !!*str or adding an if-condition, but it doesn't matter in practice.
It might make sense to output the filename given to your program on error. That might be useful for anyone trying to understand what went wrong, so perror(argv[1]).