// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (c) 2000 Silicon Graphics, Inc.  All Rights Reserved.
 *     Author: Ulrich Drepper / Nate Straz , Red Hat
 * Copyright (C) 2023 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
 */

/*\
 * This test verifies that LDT is propagated correctly from parent process to
 * the child process.
 *
 * On Friday, May 2, 2003 at 09:47:00AM MST, Ulrich Drepper wrote:
 *
 *  Robert Williamson wrote:
 *
 *  I'm getting a SIGSEGV with one of our tests, fork05.c, that apparently
 *  you wrote (attached below).  The test passes on my 2.5.68 machine running
 *  SuSE 8.0 (glibc 2.2.5 and Linuxthreads), however it segmentation faults on
 *  RedHat 9 running 2.5.68.  The test seems to "break" when it attempts to run
 *  the assembly code....could you take a look at it?
 *
 *  There is no need to look at it, I know it cannot work anymore on recent
 *  systems.  Either change all uses of %gs to %fs or skip the entire patch
 *  if %gs has a nonzero value.
 *
 * On Sat, Aug 12, 2000 at 12:47:31PM -0700, Ulrich Drepper wrote:
 *
 *  Ever since the %gs handling was fixed in the 2.3.99 series the
 *  appended test program worked.  Now with 2.4.0-test6 it's not working
 *  again.  Looking briefly over the patch from test5 to test6 I haven't
 *  seen an immediate candidate for the breakage.  It could be missing
 *  propagation of the LDT to the new process (and therefore an invalid
 *  segment descriptor) or simply clearing %gs.
 *
 *  Anyway, this is what you should see and what you get with test5:
 *
 *  a = 42
 *  %gs = 0x0007
 *  %gs = 0x0007
 *  a = 99
 *
 *  This is what you get with test6:
 *
 *  a = 42
 *  %gs = 0x0007
 *  %gs = 0x0000
 *  <SEGFAULT>
 *
 *  If somebody is actually creating a test suite for the kernel, please
 *  add this program.  It's mostly self-contained.  The correct handling
 *  of %gs is really important since glibc 2.2 will make heavy use of it.
 */

#include "tst_test.h"

#if defined(__i386__)

#include "lapi/ldt.h"

static void run(void)
{
	struct user_desc ldt0;
	int base_addr = 42;
	int status;
	pid_t pid;
	int lo;

	ldt0.entry_number = 0;
	ldt0.base_addr = (long)&base_addr;
	ldt0.limit = 4;
	ldt0.seg_32bit = 1;
	ldt0.contents = 0;
	ldt0.read_exec_only = 0;
	ldt0.limit_in_pages = 0;
	ldt0.seg_not_present = 0;
	ldt0.useable = 1;

	SAFE_MODIFY_LDT(1, &ldt0, sizeof(ldt0));

	asm volatile ("movw %w0, %%fs"::"q" (7));
	asm volatile ("movl %%fs:0, %0":"=r" (lo));
	tst_res(TINFO, "a = %d", lo);

	asm volatile ("pushl %%fs; popl %0":"=q" (lo));
	tst_res(TINFO, "%%fs = %#06hx", lo);

	asm volatile ("movl %0, %%fs:0"::"r" (99));

	pid = SAFE_FORK();
	if (!pid) {
		asm volatile ("pushl %%fs; popl %0":"=q" (lo));
		tst_res(TINFO, "%%fs = %#06hx", lo);

		asm volatile ("movl %%fs:0, %0":"=r" (lo));
		tst_res(TINFO, "a = %d", lo);

		TST_EXP_EQ_LI(lo, 99);

		exit(0);
	}

	SAFE_WAITPID(pid, &status, 0);

	if (WIFEXITED(status) && !WEXITSTATUS(status))
		tst_res(TPASS, "Child did exit with 0");
	else
		tst_res(TFAIL, "Child %s", tst_strstatus(status));
}

static struct tst_test test = {
	.test_all = run,
	.forks_child = 1,
};

#else
	TST_TEST_TCONF("Test only supports Intel 32 bits");
#endif