/*  File   : hostname.c
    Author : Richard A. O'Keefe
    Updated: 06/20/08
    Purpose: Provide a tolerably portable version of the 'hostname' command.
    Usage  : hostname -f		full  name  x.y.z
	   : hostname -s		short name  x
	   : hostname -d		domain name   y.z
	   : hostname -a		the aliases
	   : hostname                   whatever gethostname() gives you.
    Compile: cc -o hostname -O hostname.c -lnsl  (Solaris)
	   : cc -o hostname -O hostname.c	 (Mac OS X)
*/

#include <ctype.h>
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/*  extern int gethostname(char *, size_t);
    returns the host name as set by sethostname().
    sysconf(_SC_HOST_NAME_MAX) gives the maximum size of a
    host name, but we're warned that it might be "infinite".
    The simplest and most portable way seems to be to start
    with 1k and double it until it works.  Note that the
    POSIX specification does not define error results, and
    that some UNIX manuals incorrectly say int, not size_t.

    extern struct hostent *gethostbyname(char const *);
    returns a pointer to a record with
	char *h_name		should be the full name
	char **h_aliases	NULL-ptr terminated array of strings
	other stuff we don't care about.
*/

static void eputs(char const *prog, char const *line) {
    if (puts(line) < 0) {
	perror(prog);
	exit(EXIT_FAILURE);
    }
}

int main(int argc, char **argv) {
    char const opt = argc > 2           ? 'x'
                   : argc == 1          ? 'w'
                   : argv[1][0] != '-'  ? 'x'
                   : argv[1][1] == '\0' ? 'x'
                   : argv[1][2] != '\0' ? 'x' : tolower(argv[1][1]);
    char  *hname_buff = 0;
    size_t hname_size = 1024;
    char  *p;
    struct hostent *e;

    if (strchr("adfsw", opt) == 0) {
	fprintf(stderr, "Usage: %s [-[adfsw]]\n", argv[0]);
	return EXIT_FAILURE;
    }

    for (;;) {
	hname_buff = malloc(hname_size);
	if (hname_buff == 0) {
	    perror(argv[0]);
	    return EXIT_FAILURE;
	}
	if (gethostname(hname_buff, hname_size) == 0) break;
#ifdef ENAMETOOLONG
	if (errno == ENAMETOOLONG) {
	    free(hname_buff);
	    hname_size *= 2;
	    continue;
	}
#endif
	perror(argv[0]);
	return EXIT_FAILURE;
    }
    
    if (opt == 'w') {
	eputs(argv[0], hname_buff);
	return EXIT_SUCCESS;
    }
    p = strchr(hname_buff, '.');
    if (opt == 's') {
	if (p != 0) *p = '\0';
	eputs(argv[0], hname_buff);
	return EXIT_SUCCESS;
    }

    /*  Now we need a fully qualified name with dots. */
    if (p == 0 || opt == 'a') {
	e = gethostbyname(hname_buff);
	if (e == 0) {
	    char const *m;
	    /* You may well wonder why I don't use herror() here,  */
	    /* especially as we'd expect herror() to be localised. */
	    /* Answer: some platforms do not provide herror().     */
	    switch (h_errno) {
	        case HOST_NOT_FOUND: m = "host not found"; break;
	        case TRY_AGAIN     : m = "try again";      break;
	        case NO_RECOVERY   : m = "no recovery";    break;
	        case NO_DATA       : m = "no data";        break;
#if NO_ADDRESS != NO_DATA
	        case NO_ADDRESS    : m = "no address";     break;
#endif
	        case NETDB_INTERNAL: m = strerror(errno);  break;
	        default            : m = "gethostbyname() unknown error";
	    }
	    fprintf(stderr, "%s:%s: %s\n", argv[0], hname_buff, m);
	    return EXIT_FAILURE;
	}
	free(hname_buff);
	hname_buff = e->h_name;
	p = strchr(hname_buff, '.');

	if (opt == 'a') {
	    int i;
	    for (i = 0; e->h_aliases[i] != 0; i++) {
		eputs(argv[0], e->h_aliases[i]);
	    }
	    return EXIT_SUCCESS;
	}
    }

    if (opt == 'f') {
	eputs(argv[0], hname_buff);
    } else {
	if (p == 0) {
	    fprintf(stderr, "%s: no domain in hostname %s\n", argv[0], hname_buff);
	    return EXIT_FAILURE;
	}
	eputs(argv[0], p+1);
    }
    return EXIT_SUCCESS;
}

