/*
 * Copyright (C) 2005 IBM
 * Author: Serge Hallyn <serue@us.ibm.com>
 * Compile using "gcc -o utstest utstest.c"
 * Run using "for i in `seq 1 5`; do  ./utstest $i; done"
 *
 * test1:
	P1: A=gethostname
	P2: B=gethostname
	Ensure(A==B)

 * test2:
	P1: sethostname(newname); A=gethostname
	P2: (wait); B=gethostname
	Ensure (A==B)

 * test3:
	P1: A=gethostname; unshare(utsname); sethostname(newname); C=gethostname
	P2: B=gethostname; (wait); (wait); D=gethostname
	Ensure (A==B && A==D && C!=D)

 * test4:
	P1: A=gethostname; unshare(utsname); (wait); C=gethostname
	P2: B=gethostname; (wait); sethostname(newname); D=gethostname
	Ensure (A==B && A==C && C!=D)

 * test5:
	P1: A=gethostname; unshare(utsname) without suff. perms; (wait); C=gethostname
	P2: B=gethostname; (wait); sethostname(newname); D=gethostname
	Ensure (A==B==C==D) and state is ok.
 *
 */

#include <sys/wait.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

int drop_root()
{
	int ret;
	ret = setresuid(1000, 1000, 1000);
	if (ret) {
		perror("setresuid");
		exit(4);
	}
	return 1;
}

#include <linux/unistd.h>

static inline _syscall1 (int,  unshare, int, flags)

#ifndef CLONE_NEWUTS
#define CLONE_NEWUTS            0x04000000      /* New utsname group? */
#endif

int p1fd[2], p2fd[2];
pid_t cpid;
int testnum;

#define HLEN 100
#define NAME1 "serge1"
#define NAME2 "serge2"

void picknewhostname(char *orig, char *new)
{
	memset(new, 0, HLEN);
	if (strcmp(orig, NAME1) == 0)
		strcpy(new, NAME2);
	else
		strcpy(new, NAME1);
}

void P1(void)
{
	char hostname[HLEN], newhostname[HLEN], rhostname[HLEN];
	int err;
	int len;

	close(p1fd[1]);
	close(p2fd[0]);

	switch(testnum) {
	case 1:
		gethostname(hostname, HLEN);
		len = read(p1fd[0], rhostname, HLEN);
		if (len == strlen(hostname) && 
			strncmp(hostname, rhostname, len) == 0) {
			printf("test 1: success\n");
			exit(0);
		}
		printf("test 1: fail\n");
		printf("Proc 1: hostname %s\n", hostname);
		printf("test 2: hostname %s\n", rhostname);
		exit(1);
	case 2:
		gethostname(hostname, HLEN);
		picknewhostname(hostname, newhostname);
		err = sethostname(newhostname, strlen(newhostname));
		write(p2fd[1], "1", 1);
		if (err == -1) { perror("sethostname"); exit(1); }
		len = read(p1fd[0], rhostname, HLEN);
		if (len == strlen(newhostname) &&
				strncmp(newhostname, rhostname, len) == 0) {
			printf("test 2: success\n");
			exit(0);
		}
		printf("test 2: fail\n");
		printf("Proc 1: hostname %s\n", newhostname);
		printf("test 2: hostname %s\n", rhostname);
		exit(1);
	case 3:
		gethostname(hostname, HLEN);
		picknewhostname(hostname, newhostname);
		err = unshare(CLONE_NEWUTS);
		printf("unshare returned %d (should be 0)\n", err);
		err = sethostname(newhostname, strlen(newhostname));
		write(p2fd[1], "1", 1);
		if (err == -1) { perror("sethostname"); exit(1); }

		len = read(p1fd[0], rhostname, HLEN);
		if (len == strlen(newhostname) &&
				strncmp(newhostname, rhostname, len) == 0) {
			printf("test 3: fail\n");
			printf("Proc 1: hostname %s\n", newhostname);
			printf("test 2: hostname %s\n", rhostname);
			printf("These should have been different\n");
			exit(1);
		}
		if (len == strlen(hostname) &&
				strncmp(hostname, rhostname, len) == 0) {
			printf("test 3: success\n");
			exit(0);
		}
		printf("test 3: fail\n");
		printf("Proc 1: original hostname %s\n", hostname);
		printf("Proc 2: hostname %s\n", rhostname);
		printf("These should have been the same\n");
		exit(1);

	case 4:
		gethostname(hostname, HLEN);
		err = unshare(CLONE_NEWUTS);
		printf("unshare returned %d (should be 0)\n", err);
		write(p2fd[1], "1", 1); /* tell p2 to go ahead and sethostname */
		len = read(p1fd[0], rhostname, HLEN);
		gethostname(newhostname, HLEN);
		if (strcmp(hostname, newhostname) != 0) {
			printf("test 4: fail\n");
			printf("Proc 1: hostname %s\n", hostname);
			printf("Proc 1: new hostname %s\n", newhostname);
			printf("These should have been the same\n");
			exit(1);
		}
		if (strncmp(hostname, rhostname, len)==0) {
			printf("test 4: fail\n");
			printf("Proc 1: hostname %s\n", hostname);
			printf("Proc 2: new hostname %s\n", rhostname);
			printf("These should have been different\n");
			exit(1);
		}
		printf("test 4: success\n");
		exit(0);
	case 5:
		/* drop CAP_SYS_ADMIN, then do same as case 4 but check
		 * that hostname != newhostname && rhostname == newhostname */
		if (!drop_root()) {
			printf("failed to drop root.\n");
			exit(3);
		}
		gethostname(hostname, HLEN);
		err = unshare(CLONE_NEWUTS);
		printf("unshare returned %d (should be -1)\n", err);
		write(p2fd[1], "1", 1); /* tell p2 to go ahead and sethostname */
		len = read(p1fd[0], rhostname, HLEN);
		gethostname(newhostname, HLEN);
		if (strncmp(newhostname, rhostname, len)!=0) {
			printf("test 5: fail\n");
			printf("Proc 1: newhostname %s\n", newhostname);
			printf("Proc 2: new hostname %s\n", rhostname);
			printf("These should have been the same\n");
			exit(1);
		}
		if (strcmp(hostname, newhostname) == 0) {
			printf("test 5: fail\n");
			printf("Proc 1: hostname %s\n", hostname);
			printf("Proc 1: new hostname %s\n", newhostname);
			printf("These should have been different\n");
			exit(1);
		}
		printf("test 5: success\n");
		exit(0);
	default:
		break;
	}
	return;
}

void P2(void)
{
	char hostname[HLEN], newhostname[HLEN];
	int len;
	int err;

	close(p1fd[0]);
	close(p2fd[1]);

	switch(testnum) {
	case 1:
		gethostname(hostname, HLEN);
		write(p1fd[1], hostname, strlen(hostname));
		break;
	case 2:
	case 3:
		len = 0;
		while (!len) {
			len = read(p2fd[0], hostname, 1);
		}
		gethostname(hostname, HLEN);
		write(p1fd[1], hostname, strlen(hostname));
		break;
	case 4:
	case 5:
		len = 0;
		while (!len) {
			len = read(p2fd[0], hostname, 1);
		}
		gethostname(hostname, HLEN);
		picknewhostname(hostname, newhostname);
		sethostname(newhostname, strlen(newhostname));
		write(p1fd[1], newhostname, strlen(newhostname));
		break;
	default:
		printf("undefined test: %d\n", testnum);
		break;
	}
	return;
}

int main(int argc, char *argv[])
{
	if (argc != 2) {
		printf("Usage: %s <testnum>\n", argv[0]);
		printf(" where testnum is between 1 and 5 inclusive\n");
		exit(2);
	}
	if (pipe(p1fd) == -1) { perror("pipe"); exit(EXIT_FAILURE); }
	if (pipe(p2fd) == -1) { perror("pipe"); exit(EXIT_FAILURE); }

	testnum = atoi(argv[1]);
	if (testnum < 1 || testnum > 5) {
		printf("testnum should be between 1 and 5 inclusive.\n");
		exit(2);
	}

	cpid = fork();

	if (cpid == -1) { perror("fork"); exit(EXIT_FAILURE); }

	if (cpid == 0)
		P1();
	else
		P2();

	return 0;
}

