fix various fs correctness and perf issues

This is a grab-bag of fs correctness and perf improvements. It'll be easier to review each individual commit separately.

This doesn't actually change the behavior, since it used to call `resolve(-1)` if *fs.truncate* passed `false` to the callback because the path was a directory; and now it calls `resolve(fd)`, where *fd* is `-1`, if *fs.open* passes `-1` because the path is a directory.