4) I am new to ioctl-based programming, so can anyone please tell me what is
awful about it?
The biggest problem with ioctl is by FAR that people get it wrong. ioctl is the equivalent of typing everything in C void * and wondering why your program isn't behaving correctly. Look at ioctl vs getsockopt() and setsockopt()
int ioctl(int d, int request, ...);
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
They provide the same ability to be generic and to move data back and forth but the socket functions encode size and direction into the call. It means you can easily do sane checks in the kernel.
Linus has recently pushed a bit that syscalls are the right way to go (not in this discussion, just in general discussions about kernel/userspace ABI). A good syscall is going to provide size, direction, and strong typing of arguments.
The more information an interface encodes and enforces the more likely it is that the interface will be used correctly.