This is an interactive, menu-driven program. I would expect that runtime failures would result in a return to the menu. But instead, we unceremoniously dump the user back to their shell without opportunity to save their work so far:
if (!n) { perror("malloc"); exit(EXIT_FAILURE); }
perror() is inappropriate here, since malloc() is not specified to set errno on failure (POSIX systems are required to, but in the absence of other indicators, I'm assuming this is intended to be a portable program).
When allocating memory, it's easier to match the requested type if we use the assigned-to variable to compute. In other words, rather than
*cur = malloc(sizeof(TreeNode));
we would write
*cur = malloc(sizeof **cur);
validateDate() allows dates to be specified billions of years past and future - is that really reasonable?
insertTreeByDate() needlessly repeats the logic of reservationCompare(); why not just call it?
while (*cur) {
cur = reservationCompare(r, &(*cur)->r) < 0 ?
&(*cur)->left : &(*cur)->right;
}
readLineTrim() uses undeclared type ssize_t and function getline(). Perhaps this isn't a portable C program as advertised? It should also have a comment explaining how errors are reported when reading standard input fails - it's not at all clear what to test.
Similarly, inputReservation() should be clearer that a reservation with {0, 0, 0} as its date is a sentinel value rather than a real return value (this one I did manage to deduce from the code).
Instead of repeatedly allocating in the inputReservation() loop, it would be better to re-use the storage when possible, and only free() it after the loop. This loop appears to busy-lockup when we run out of input - that certainly needs to be fixed.
saveCSV() is rather rude, overwriting any existing file without warning.
loadCSV() doesn't deal with malformed input very well, since atoi() just returns 0 for unparseable strings - perhaps sscanf() would be more appropriate, and more obviously match how we read user input?
This function also treats read errors as EOF, so could result in reading just part of the file.
printListRecursive is almost the same as the writing loop in saveCSV(). It should be simple to make them share code (perhaps even consider changing the file format so they can use the same format string).
The main menu treats unrecognised input as "save" - probably better instead to emit an error message instead. I recommend switch instead of the if/else if chain.
What's most unclear to me is why we duplicate the bookings as a list, when we already have a tree which gives much more efficient access in sorted order.