All of lore.kernel.org
 help / color / mirror / Atom feed
From: Cyril Hrubis <chrubis@suse.cz>
To: ltp@lists.linux.it
Subject: [LTP] [PATCH v2 5/6] syscalls: added memfd_create dir and memfd_create/memfd_create01.c
Date: Tue, 7 Mar 2017 13:17:56 +0100	[thread overview]
Message-ID: <20170307121756.GC1261@rei.lan> (raw)
In-Reply-To: <1488822340-29259-6-git-send-email-jracek@redhat.com>

Hi!
> +/*
> + * Do few basic sealing tests to see whether setting/retrieving seals works.
> + */
> +static void test_basic(int fd)
> +{
> +	/* add basic seals */
> +	CHECK_RESULT(mfd_assert_has_seals(fd, 0));
> +	CHECK_RESULT(mfd_assert_add_seals(fd, F_SEAL_SHRINK |
> +			F_SEAL_WRITE));
> +	CHECK_RESULT(mfd_assert_has_seals(fd, F_SEAL_SHRINK |
> +			F_SEAL_WRITE));
> +
> +	/* add them again */
> +	CHECK_RESULT(mfd_assert_add_seals(fd, F_SEAL_SHRINK |
> +			F_SEAL_WRITE));
> +	CHECK_RESULT(mfd_assert_has_seals(fd, F_SEAL_SHRINK |
> +			F_SEAL_WRITE));
> +
> +	/* add more seals and seal against sealing */
> +	CHECK_RESULT(mfd_assert_add_seals(fd, F_SEAL_GROW | F_SEAL_SEAL));
> +	CHECK_RESULT(mfd_assert_has_seals(fd, F_SEAL_SHRINK |
> +			F_SEAL_GROW |
> +			F_SEAL_WRITE |
> +			F_SEAL_SEAL));
> +
> +	/* verify that sealing no longer works */
> +	CHECK_RESULT(mfd_fail_add_seals(fd, F_SEAL_GROW));
> +	CHECK_RESULT(mfd_fail_add_seals(fd, 0));
> +
> +	tst_res(TPASS, "%s", __func__);
> +}
> +
> +/*
> + * Verify that no sealing is possible when memfd is created without
> + * MFD_ALLOW_SEALING flag.
> + */
> +static void test_no_sealing_without_flag(int fd)
> +{
> +	CHECK_RESULT(mfd_assert_has_seals(fd, F_SEAL_SEAL));
> +	CHECK_RESULT(mfd_fail_add_seals(fd, F_SEAL_SHRINK |
> +			F_SEAL_GROW |
> +			F_SEAL_WRITE));
> +	CHECK_RESULT(mfd_assert_has_seals(fd, F_SEAL_SEAL));
> +
> +	tst_res(TPASS, "%s", __func__);
> +}
> +
> +/*
> + * Test SEAL_WRITE
> + * Test whether SEAL_WRITE actually prevents modifications.
> + */
> +static void test_seal_write(int fd)
> +{
> +	CHECK_RESULT(mfd_assert_has_seals(fd, 0));
> +	CHECK_RESULT(mfd_assert_add_seals(fd, F_SEAL_WRITE));
> +	CHECK_RESULT(mfd_assert_has_seals(fd, F_SEAL_WRITE));
> +
> +	CHECK_RESULT(mfd_assert_read(fd));
> +	CHECK_RESULT(mfd_fail_write(fd));
> +	CHECK_RESULT(mfd_assert_shrink(fd));
> +	CHECK_RESULT(mfd_assert_grow(fd));
> +	CHECK_RESULT(mfd_fail_grow_write(fd));
> +
> +	tst_res(TPASS, "%s", __func__);
> +}
> +
> +/*
> + * Test SEAL_SHRINK
> + * Test whether SEAL_SHRINK actually prevents shrinking
> + */
> +static void test_seal_shrink(int fd)
> +{
> +	CHECK_RESULT(mfd_assert_has_seals(fd, 0));
> +	CHECK_RESULT(mfd_assert_add_seals(fd, F_SEAL_SHRINK));
> +	CHECK_RESULT(mfd_assert_has_seals(fd, F_SEAL_SHRINK));
> +
> +	CHECK_RESULT(mfd_assert_read(fd));
> +	CHECK_RESULT(mfd_assert_write(fd));
> +	CHECK_RESULT(mfd_fail_shrink(fd));
> +	CHECK_RESULT(mfd_assert_grow(fd));
> +	CHECK_RESULT(mfd_assert_grow_write(fd));
> +
> +	tst_res(TPASS, "%s", __func__);
> +}
> +
> +/*
> + * Test SEAL_GROW
> + * Test whether SEAL_GROW actually prevents growing
> + */
> +static void test_seal_grow(int fd)
> +{
> +	CHECK_RESULT(mfd_assert_has_seals(fd, 0));
> +	CHECK_RESULT(mfd_assert_add_seals(fd, F_SEAL_GROW));
> +	CHECK_RESULT(mfd_assert_has_seals(fd, F_SEAL_GROW));
> +
> +	CHECK_RESULT(mfd_assert_read(fd));
> +	CHECK_RESULT(mfd_assert_write(fd));
> +	CHECK_RESULT(mfd_assert_shrink(fd));
> +	CHECK_RESULT(mfd_fail_grow(fd));
> +	CHECK_RESULT(mfd_fail_grow_write(fd));
> +
> +	tst_res(TPASS, "%s", __func__);
> +}
> +
> +/*
> + * Test SEAL_SHRINK | SEAL_GROW
> + * Test whether SEAL_SHRINK | SEAL_GROW actually prevents resizing
> + */
> +static void test_seal_resize(int fd)
> +{
> +	CHECK_RESULT(mfd_assert_has_seals(fd, 0));
> +	CHECK_RESULT(mfd_assert_add_seals(fd, F_SEAL_SHRINK | F_SEAL_GROW));
> +	CHECK_RESULT(mfd_assert_has_seals(fd, F_SEAL_SHRINK | F_SEAL_GROW));
> +
> +	CHECK_RESULT(mfd_assert_read(fd));
> +	CHECK_RESULT(mfd_assert_write(fd));
> +	CHECK_RESULT(mfd_fail_shrink(fd));
> +	CHECK_RESULT(mfd_fail_grow(fd));
> +	CHECK_RESULT(mfd_fail_grow_write(fd));
> +
> +	tst_res(TPASS, "%s", __func__);
> +}
> +
> +/*
> + * Test sharing via dup()
> + * Test that seals are shared between dupped FDs and they're all equal.
> + */
> +static void test_share_dup(int fd)
> +{
> +	int fd2;
> +
> +	CHECK_RESULT(mfd_assert_has_seals(fd, 0));
> +
> +	fd2 = SAFE_DUP(fd);
> +	CHECK_RESULT(mfd_assert_has_seals(fd2, 0));
> +
> +	CHECK_RESULT(mfd_assert_add_seals(fd, F_SEAL_WRITE));
> +	CHECK_RESULT(mfd_assert_has_seals(fd, F_SEAL_WRITE));
> +	CHECK_RESULT(mfd_assert_has_seals(fd2, F_SEAL_WRITE));
> +
> +	CHECK_RESULT(mfd_assert_add_seals(fd2, F_SEAL_SHRINK));
> +	CHECK_RESULT(mfd_assert_has_seals(fd, F_SEAL_WRITE | F_SEAL_SHRINK));
> +	CHECK_RESULT(mfd_assert_has_seals(fd2, F_SEAL_WRITE | F_SEAL_SHRINK));
> +
> +	CHECK_RESULT(mfd_assert_add_seals(fd, F_SEAL_SEAL));
> +	CHECK_RESULT(mfd_assert_has_seals(fd,
> +		F_SEAL_WRITE | F_SEAL_SHRINK | F_SEAL_SEAL));
> +	CHECK_RESULT(mfd_assert_has_seals(fd2,
> +		F_SEAL_WRITE | F_SEAL_SHRINK | F_SEAL_SEAL));
> +
> +	CHECK_RESULT(mfd_fail_add_seals(fd, F_SEAL_GROW));
> +	CHECK_RESULT(mfd_fail_add_seals(fd2, F_SEAL_GROW));
> +	CHECK_RESULT(mfd_fail_add_seals(fd, F_SEAL_SEAL));
> +	CHECK_RESULT(mfd_fail_add_seals(fd2, F_SEAL_SEAL));
> +
> +	SAFE_CLOSE(fd2);
> +
> +	CHECK_RESULT(mfd_fail_add_seals(fd, F_SEAL_GROW));
> +
> +	tst_res(TPASS, "%s", __func__);
> +}
> +
> +/*
> + * Test sealing with active mmap()s
> + * Modifying seals is only allowed if no other mmap() refs exist.
> + */
> +static void test_share_mmap(int fd)
> +{
> +	void *p;
> +
> +	CHECK_RESULT(mfd_assert_has_seals(fd, 0));
> +
> +	/* shared/writable ref prevents sealing WRITE, but allows others */
> +	p = SAFE_MMAP(NULL,
> +			MFD_DEF_SIZE,
> +			PROT_READ | PROT_WRITE,
> +			MAP_SHARED,
> +			fd,
> +			0);
> +	CHECK_RESULT(mfd_fail_add_seals(fd, F_SEAL_WRITE));
> +	CHECK_RESULT(mfd_assert_has_seals(fd, 0));
> +	CHECK_RESULT(mfd_assert_add_seals(fd, F_SEAL_SHRINK));
> +	CHECK_RESULT(mfd_assert_has_seals(fd, F_SEAL_SHRINK));
> +	SAFE_MUNMAP(p, MFD_DEF_SIZE);
> +
> +	/* readable ref allows sealing */
> +	p = SAFE_MMAP(NULL,
> +			MFD_DEF_SIZE,
> +			PROT_READ,
> +			MAP_PRIVATE,
> +			fd,
> +			0);
> +	CHECK_RESULT(mfd_assert_add_seals(fd, F_SEAL_WRITE));
> +	CHECK_RESULT(mfd_assert_has_seals(fd, F_SEAL_WRITE | F_SEAL_SHRINK));
> +	SAFE_MUNMAP(p, MFD_DEF_SIZE);
> +
> +	tst_res(TPASS, "%s", __func__);
> +}
> +
> +/*
> + * Test sealing with open(/proc/self/fd/%d)
> + * Via /proc we can get access to a separate file-context for the same memfd.
> + * This is *not* like dup(), but like a real separate open(). Make sure the
> + * semantics are as expected and we correctly check for RDONLY / WRONLY / RDWR.
> + */
> +static void test_share_open(int fd)
> +{
> +	int fd2;
> +
> +	CHECK_RESULT(mfd_assert_has_seals(fd, 0));
> +
> +	fd2 = CHECK_RESULT(mfd_assert_open(fd, O_RDWR, 0));
> +	CHECK_RESULT(mfd_assert_add_seals(fd, F_SEAL_WRITE));
> +	CHECK_RESULT(mfd_assert_has_seals(fd, F_SEAL_WRITE));
> +	CHECK_RESULT(mfd_assert_has_seals(fd2, F_SEAL_WRITE));
> +
> +	CHECK_RESULT(mfd_assert_add_seals(fd2, F_SEAL_SHRINK));
> +	CHECK_RESULT(mfd_assert_has_seals(fd, F_SEAL_WRITE | F_SEAL_SHRINK));
> +	CHECK_RESULT(mfd_assert_has_seals(fd2, F_SEAL_WRITE | F_SEAL_SHRINK));
> +
> +	SAFE_CLOSE(fd);
> +	fd = CHECK_RESULT(mfd_assert_open(fd2, O_RDONLY, 0));
> +
> +	CHECK_RESULT(mfd_fail_add_seals(fd, F_SEAL_SEAL));
> +	CHECK_RESULT(mfd_assert_has_seals(fd, F_SEAL_WRITE | F_SEAL_SHRINK));
> +	CHECK_RESULT(mfd_assert_has_seals(fd2, F_SEAL_WRITE | F_SEAL_SHRINK));
> +
> +	SAFE_CLOSE(fd2);
> +
> +	tst_res(TPASS, "%s", __func__);
> +}
> +
> +static const struct tcase {
> +	int flags;
> +	void (*func)(int fd);
> +} tcases[] = {
> +	{MFD_ALLOW_SEALING, &test_basic},
> +	{0,                 &test_no_sealing_without_flag},
> +
> +	{MFD_ALLOW_SEALING, &test_seal_write},
> +	{MFD_ALLOW_SEALING, &test_seal_shrink},
> +	{MFD_ALLOW_SEALING, &test_seal_grow},
> +	{MFD_ALLOW_SEALING, &test_seal_resize},
> +
> +	{MFD_ALLOW_SEALING, &test_share_dup},
> +	{MFD_ALLOW_SEALING, &test_share_mmap},
> +	{MFD_ALLOW_SEALING, &test_share_open},
> +};
> +
> +static void verify_memfd_create(unsigned int n)
> +{
> +	const struct tcase *tc;
> +
> +	tc = &tcases[n];
> +
> +	TEST(mfd_assert_new(TCID, MFD_DEF_SIZE, tc->flags));
> +	if (TEST_RETURN < 0)
> +		tst_brk(TBROK | TERRNO, "memfd_create() failed unexpectedly");
> +
> +	tc->func(TEST_RETURN);
> +
> +	if (TEST_RETURN > 0)
> +		SAFE_CLOSE(TEST_RETURN);
> +}
> +
> +static void setup(void)
> +{
> +	ASSERT_HAVE_MEMFD_CREATE();
> +}
> +
> +static struct tst_test test = {
> +	.tid = "memfd_create01",
> +	.test = verify_memfd_create,
> +	.tcnt = ARRAY_SIZE(tcases),
> +	.setup = setup,
> +};
> diff --git a/testcases/kernel/syscalls/memfd_create/memfd_create_common.h b/testcases/kernel/syscalls/memfd_create/memfd_create_common.h
> new file mode 100644
> index 0000000..f07cc8c
> --- /dev/null
> +++ b/testcases/kernel/syscalls/memfd_create/memfd_create_common.h
> @@ -0,0 +1,525 @@
> +/*
> + * Copyright (C) 2017  Red Hat, Inc.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation; either version 2 of
> + * the License, or (at your option) any later version.
> + *
> + * This program is distributed in the hope that it would be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + */
> +
> +#ifndef MEMFD_TEST_COMMON
> +#define MEMFD_TEST_COMMON
> +
> +#include <sys/types.h>
> +#include <sys/syscall.h>
> +#include <sys/uio.h>
> +#include <lapi/fallocate.h>
> +#include <lapi/fcntl.h>
> +#include <lapi/memfd.h>
> +#include <errno.h>
> +#include <string.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +
> +#define TST_NO_DEFAULT_MAIN
> +#include <tst_test.h>
> +
> +#include "linux_syscall_numbers.h"
> +
> +#define MFD_DEF_SIZE 8192
> +#define STACK_SIZE 65536
> +
> +enum assert_retval {
> +	ALL_OK,
> +	MEMFD_FAIL,
> +	MEMFD_MISMATCH,
> +	FTRUNCATE_FAIL,
> +	FCNTL_FAIL,
> +	GET_SEALS_MISMATCH,
> +	ADD_SEALS_FAIL,
> +	ADD_SEALS_MISMATCH,
> +	FSTAT_FAIL,
> +	FILE_SIZE_MISMATCH,
> +	OPEN_MISMATCH,
> +	OPEN_FAIL,
> +	MPROTECT_FAIL,
> +	FALLOCATE_PUNCH_FAIL,
> +	WRITE_FAIL,
> +	WRITE_MISMATCH,
> +	MUNMAP_FAIL,
> +	MMAP_FAIL,
> +	MMAP_MISMATCH,
> +	MMAP_MPROT_MISMATCH,
> +	FALLOCATE_PUNCH_MISMATCH,
> +	FTRUNCATE_SHRINK_MISMATCH,
> +	FALLOCATE_ALLOC_FAIL,
> +	FTRUNCATE_GROW_MISMATCH,
> +	FALLOCATE_ALLOC_MISMATCH,
> +	PWRITE_FAIL,
> +	PWRITE_MISMATCH,
> +	READ_FAIL,
> +	READ_MISMATCH,
> +	CLOSE_FAIL,
> +};
> +
> +const char *err_msg[] = {
> +	"all OK",
> +	"memfd_create() failed",
> +	"memfd_create() succeeded, but failure expected",
> +	"ftruncate() failed",
> +	"fcntl() failed",
> +	"GET_SEALS() returned unexpected value",
> +	"ADD_SEALS() failed",
> +	"ADD_SEALS() didn't fail as expected",
> +	"fstat() failed",
> +	"wrong file size",
> +	"open() didn't fail as expected",
> +	"open() failed",
> +	"mprotect() failed",
> +	"fallocate(PUNCH_HOLE) failed",
> +	"write() failed",
> +	"write() didn't fail as expected",
> +	"munmap() failed",
> +	"mmap() failed",
> +	"mmap() didn't fail as expected",
> +	"mmap()+mprotect() didn't fail as expected",
> +	"fallocate(PUNCH_HOLE) didn't fail as expected",
> +	"ftruncate(SHRINK) didn't fail as expected",
> +	"fallocate(ALLOC) failed",
> +	"ftruncate(GROW) didn't fail as expected",
> +	"fallocate(ALLOC) didn't fail as expected",
> +	"pwrite() failed",
> +	"pwrite() didn't fail as expected",
> +	"read() failed",
> +	"read() didn't fail as expected",
> +	"close() failed",
> +};
> +
> +#define STRINGIFY(A) #A
> +
> +#define CHECK_RESULT(func2call) ({ \
> +	int result; \
> +	result = func2call; \
> +	if (result < ALL_OK) \
> +		tst_brk(TFAIL, "%s: %s", STRINGIFY(func2call), \
> +			err_msg[-result]); \
> +	else \
> +		tst_res(TPASS, "%s", STRINGIFY(func2call)); \
> +	result; \
> +})
> +
> +#define ASSERT_HAVE_MEMFD_CREATE() do { \
> +	int r; \
> +	r = mfd_assert_new("dummy_call", 0, 0); \
> +	if (r < 0) \
> +		tst_brk(TCONF, "memfd_create does not exist on your system."); \
> +		SAFE_CLOSE(r); \
                ^
		The indentation here is confusing.

> +	} while (0)
> +
> +static int sys_memfd_create(const char *name,
> +		unsigned int flags)
> +{
> +	return syscall(__NR_memfd_create, name, flags);
> +}
> +
> +static int mfd_assert_new(const char *name, loff_t sz, unsigned int flags)
> +{
> +	int fd, r;
> +
> +	fd = sys_memfd_create(name, flags);
> +	if (fd < 0)
> +		return -MEMFD_FAIL;
> +
> +	r = ftruncate(fd, sz);
> +	if (r < 0)
> +		return -FTRUNCATE_FAIL;
> +
> +	return fd;
> +}

This indirection (returning error number and matching it agains table is
confusing as well).

I wonder if we should do this similarily to safe macros, i.e. doing
something as:

#define SAFE_MFD_NEW(name, sz, flags) \
        safe_mfd_new(__FILE__, __LINE__, name, flags);

static int safe_mfd_new(const char *filename, const char lineno,
                        const char *name, unsigned int flags)
{
	int fd;

	if (sys_memfd_create(name, flags)) {
		tst_brk(TBROK | TERRNO,
		        "%s:%i: memfd_create(%s,%u) failed",
			file, lineno, name, flags);
	}

	return fd;
}

Then we could do something as:

	int fd;

	fd = SAFE_MFD_NEW("foo", 0);
	SAFE_FTRUNCATE(fd, sz);

In the main test function, which seems to be much clearer code to me.

> +static int mfd_fail_new(const char *name, unsigned int flags)
> +{
> +	int fd;
> +
> +	fd = sys_memfd_create(name, flags);
> +	if (fd >= 0) {
> +		close(fd);
> +		return -MEMFD_MISMATCH;
> +	}
> +
> +	return fd;
> +}
> +
> +static int mfd_assert_get_seals(int fd)
> +{
> +	int r;
> +
> +	r = fcntl(fd, F_GET_SEALS);
> +	if (r < 0)
> +		return -FCNTL_FAIL;
> +
> +	return r;
> +}

This could be just replaced with SAFE_FCNTL() in the test code.

> +static int mfd_assert_has_seals(int fd, int seals)
> +{
> +	int s;
> +
> +	s = mfd_assert_get_seals(fd);
> +	if (s < 0)
> +		return s;
> +
> +	if (s != seals)
> +		return -GET_SEALS_MISMATCH;
> +
> +	return ALL_OK;
> +}

This as well, you can even just do:

	int s;

	s = SAFE_FCNTL(fd, F_GET_SEALS);

	if (seals != s) {
		tst_res(TFAIL, "Unexpected seals %i, expected %i",
		         seals, s);
	}

> +static int mfd_assert_add_seals(int fd, int seals)
> +{
> +	int r;
> +
> +	r = mfd_assert_get_seals(fd);
> +	if (r < 0)
> +		return r;
> +
> +	r = fcntl(fd, F_ADD_SEALS, seals);
> +	if (r < 0)
> +		return -ADD_SEALS_FAIL;
> +
> +	return ALL_OK;
> +}

Again this is just one SAFE_FCNTL();

> +static int mfd_fail_add_seals(int fd, int seals)
> +{
> +	int r;
> +	int s;
> +
> +	r = fcntl(fd, F_GET_SEALS);
> +	if (r < 0)
> +		s = 0;
> +	else
> +		s = r;

This does not make any sense, is the s variable used for anything at
all?

> +	r = fcntl(fd, F_ADD_SEALS, seals);
> +	if (r >= 0)
> +		return -ADD_SEALS_MISMATCH;
> +
> +	return ALL_OK;
> +}

And we can so something as:

#define FAIL_ADD_SEALS(fd, seals) \
        fail_add_seals(__FILE__, __LINE__, fd, seals)

static void fail_add_seals(const char *file, const int lineno,
                           int fd, int seals)
{
	if (fcntl(fd, F_ADD_SEALS, seals) >= 0) {
		tst_res(TFAIL,
		        "%s:%i: fcntl(%i, F_ADD_SEALS, %i) passed unexpectedly",
			file, lineno, fd, seals);
	} else {
		tst_res(TPASS, "%s:%i: fcntl(%i, F_ADD_SEALS, %i) failed",
		        file, lineno, fd, seals);
	}
}

For the negative testcases.

> +static int mfd_assert_size(int fd, size_t size)
> +{
> +	struct stat st;
> +	int r;
> +
> +	r = fstat(fd, &st);
> +	if (r < 0)
> +		return -FSTAT_FAIL;
> +	else if (st.st_size != (long)size)
> +		return -FILE_SIZE_MISMATCH;
> +
> +	return ALL_OK;
> +}

We have SAFE_FSTAT() as well. etc.

-- 
Cyril Hrubis
chrubis@suse.cz

  reply	other threads:[~2017-03-07 12:17 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-03-06 17:45 [LTP] [PATCH v2 0/6] Added memfd_create() testsuite Jakub Racek
2017-03-06 17:45 ` [LTP] [PATCH v2 1/6] Added syscall numbers for memfd_create Jakub Racek
2017-03-06 17:45 ` [LTP] [PATCH v2 2/6] Added memfd_create() lapi flags Jakub Racek
2017-03-06 17:45 ` [LTP] [PATCH v2 3/6] Added fcntl() " Jakub Racek
2017-03-06 17:45 ` [LTP] [PATCH v2 4/6] Added fallocate() flags and wrapper to lapi Jakub Racek
2017-03-07 11:44   ` Cyril Hrubis
2017-03-06 17:45 ` [LTP] [PATCH v2 5/6] syscalls: added memfd_create dir and memfd_create/memfd_create01.c Jakub Racek
2017-03-07 12:17   ` Cyril Hrubis [this message]
2017-03-07 13:08     ` Jakub =?unknown-8bit?q?Ra=C4=8Dek?=
2017-03-07 13:21       ` Cyril Hrubis
2017-03-06 17:45 ` [LTP] [PATCH v2 6/6] syscalls/memfd_create02.c: added new test Jakub Racek
2017-03-07 12:21   ` Cyril Hrubis

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20170307121756.GC1261@rei.lan \
    --to=chrubis@suse.cz \
    --cc=ltp@lists.linux.it \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.