/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2018 Joyent, Inc.
 */

/*
 * Some basic pthread name API tests.
 */

#include <sys/stat.h>
#include <pthread.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <thread.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <err.h>


/*ARGSUSED*/
static void *
thr(void *unused)
{
	(void) sleep(100);
	return (NULL);
}

/*ARGSUSED*/
int
main(int argc, char *argv[])
{
	char name[PTHREAD_MAX_NAMELEN_NP];
	pthread_attr_t attr;
	char path[PATH_MAX];
	pthread_t tid;
	ssize_t n;
	int test;
	int rc;
	int fd;

	/* Default thread name is empty string. */
	test = 1;

	rc = pthread_getname_np(pthread_self(), name, sizeof (name));

	if (rc != 0 || strcmp(name, "") != 0)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	/* Can set name. */
	test = 2;

	(void) strlcpy(name, "main", sizeof (name));
	rc = pthread_setname_np(pthread_self(), name);

	if (rc != 0)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	rc = pthread_getname_np(pthread_self(), name, sizeof (name));

	if (rc != 0 || strcmp(name, "main") != 0)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	/* ERANGE check. */
	test = 3;

	rc = pthread_getname_np(pthread_self(), name, 2);

	if (rc != ERANGE)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	/* EINVAL check. */
	test = 4;

	rc = pthread_getname_np(pthread_self(), NULL, sizeof (name));

	if (rc != EINVAL)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	/* can clear thread name. */
	test = 5;

	rc = pthread_setname_np(pthread_self(), NULL);

	if (rc != 0)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	rc = pthread_getname_np(pthread_self(), name, sizeof (name));

	if (rc != 0 || strcmp(name, "") != 0)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	/* non-existent thread check. */
	test = 6;

	rc = pthread_getname_np(808, name, sizeof (name));

	if (rc != ESRCH)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	rc = pthread_setname_np(808, "state");

	if (rc != ESRCH)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	/* too long a name. */
	test = 7;

	rc = pthread_setname_np(pthread_self(),
	    "12345678901234567890123456789012");

	if (rc != ERANGE)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	/* can name another thread. */
	test = 8;

	rc = pthread_create(&tid, NULL, thr, NULL);

	if (rc != 0)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	rc = pthread_setname_np(tid, "otherthread");

	if (rc != 0)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	/* attr tests. */
	test = 9;

	(void) pthread_attr_init(&attr);

	rc = pthread_attr_setname_np(&attr,
	    "12345678901234567890123456789012");

	if (rc != ERANGE)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	rc = pthread_attr_setname_np(&attr, "thread2");

	if (rc != 0)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	rc = pthread_attr_getname_np(&attr, NULL, sizeof (name));

	if (rc != EINVAL)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	rc = pthread_attr_getname_np(&attr, name, 2);

	if (rc != ERANGE)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	/* does the attr actually apply? */
	test = 10;

	rc = pthread_create(&tid, &attr, thr, NULL);

	if (rc != 0)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	rc = pthread_getname_np(tid, name, sizeof (name));

	if (rc != 0 || strcmp(name, "thread2") != 0)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	/* proc read tests */
	test = 11;

	(void) snprintf(path, sizeof (path),
	    "/proc/self/lwp/%d/lwpname", (int)tid);

	fd = open(path, O_RDWR);

	if (fd == -1)
		errx(EXIT_FAILURE, "test %d failed with %d", test, errno);

	n = read(fd, name, sizeof (name));

	if (n != sizeof (name) || strcmp(name, "thread2") != 0)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	if (lseek(fd, 0, SEEK_SET) != 0)
		errx(EXIT_FAILURE, "test %d failed with %d", test, errno);

	n = read(fd, name, PTHREAD_MAX_NAMELEN_NP * 2);

	if (n != sizeof (name) || strcmp(name, "thread2") != 0)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	if (lseek(fd, 0, SEEK_SET) != 0)
		errx(EXIT_FAILURE, "test %d failed with %d", test, errno);

	n = read(fd, name, 4);

	if (n != 4 || strncmp(name, "thre", 4) != 0)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	/* proc write tests */
	test = 12;

	if (lseek(fd, 0, SEEK_SET) != 0)
		errx(EXIT_FAILURE, "test %d failed with %d", test, errno);

	n = write(fd, "1234567890123456789012345678901",
	    PTHREAD_MAX_NAMELEN_NP);

	if (n != PTHREAD_MAX_NAMELEN_NP)
		errx(EXIT_FAILURE, "test %d failed with %d", test, errno);

	if (lseek(fd, 0, SEEK_SET) != 0)
		errx(EXIT_FAILURE, "test %d failed with %d", test, errno);

	n = write(fd, "foo", sizeof ("foo"));

	if (n != sizeof ("foo"))
		errx(EXIT_FAILURE, "test %d failed with %d", test, errno);

	if (lseek(fd, 0, SEEK_SET) != 0)
		errx(EXIT_FAILURE, "test %d failed with %d", test, errno);

	n = read(fd, name, sizeof (name));

	if (n != sizeof (name) || strcmp(name, "foo") != 0)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	(void) close(fd);

	/* thr_* API. */
	test = 13;

	rc = thr_setname(thr_self(), "main");

	if (rc != 0)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	rc = thr_getname(thr_self(), name, sizeof (name));

	if (rc != 0 || strcmp(name, "main") != 0)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	/* badness */
	test = 14;

	rc = thr_setname(thr_self(), "\033]0;messeduptitle\a");

	if (rc != EINVAL)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	rc = thr_setname(thr_self(), "ab\177\177\n");

	if (rc != EINVAL)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	rc = pthread_attr_setname_np(&attr, "\033]0;messeduptitle\a");

	if (rc != EINVAL)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	rc = pthread_attr_setname_np(&attr, "ab\177\177\n");

	if (rc != EINVAL)
		errx(EXIT_FAILURE, "test %d failed with %d", test, rc);

	return (EXIT_SUCCESS);
}