mailing list of musl libc
 help / color / mirror / code / Atom feed
* [musl] [PATCH 1/3 libc-test] functional:time:clock_nanosleep test
@ 2024-11-05 22:44 Ryan Gardner
  2024-11-05 22:44 ` [musl] [PATCH 2/3 libc-test] functional:time:timer_delete test Ryan Gardner
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Ryan Gardner @ 2024-11-05 22:44 UTC (permalink / raw)
  To: musl; +Cc: Ryan Gardner

Testing of musl API against POSIX 2008 standard.

Tests added:

- EINTR is returned when sleep is interupted by a signal
- ENOTSUP is returned when clock_id specifies an unsupported clock
- EINVAL is returned when tv_nsec is out of range
- EINVAL is returned when clock_id specifies an unknown clock
- EINVAL is returned when clokc_id specifies the CPU-time clock of calling thread
- rmtp is set to the remaining unslept time when interupted.
- time elapses as expected

References:
- https://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_nanosleep.html

Signed-off-by: Ryan Gardner <ryan.p.gardner@boeing.com>
---
 src/functional/clock_nanosleep.c | 133 +++++++++++++++++++++++++++++++
 1 file changed, 133 insertions(+)
 create mode 100644 src/functional/clock_nanosleep.c

diff --git a/src/functional/clock_nanosleep.c b/src/functional/clock_nanosleep.c
new file mode 100644
index 0000000..071000a
--- /dev/null
+++ b/src/functional/clock_nanosleep.c
@@ -0,0 +1,133 @@
+/*
+ * clock_nanosleep unit test
+ */
+
+#include "test.h"
+#include <errno.h>
+#include <signal.h>
+#include <string.h>
+#include <time.h>
+
+#define MAX_NSEC 1000000000
+#define SLEEP_NANO 500000
+#define TEST(c, ...) ((c) || (t_error(#c " failed: " __VA_ARGS__), 0))
+
+static void dummy_signal_handler(int signum) {}
+
+static void test_time_elapsed(void)
+{
+	// get start time
+	struct timespec time1;
+	if (clock_gettime(CLOCK_MONOTONIC, &time1) == -1) {
+		t_error("clock_gettime() failed. Errno: %s\n", strerror(errno));
+		return;
+	}
+
+	struct timespec ts = {0, SLEEP_NANO};
+	TEST(clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, &ts) == 0,
+	     "clock_nanosleep failed with clock id %d\n", CLOCK_MONOTONIC);
+
+	// get finish time
+	struct timespec time2;
+	if (clock_gettime(CLOCK_MONOTONIC, &time2) == -1) {
+		t_error("clock_gettime() failed. Errno: %s\n", strerror(errno));
+		return;
+	}
+
+	// calculate elapsed time
+	time_t elapsed = time2.tv_sec > time1.tv_sec
+	                     ? time2.tv_nsec + (MAX_NSEC - time1.tv_nsec)
+	                     : time2.tv_nsec - time1.tv_nsec;
+
+	TEST(elapsed >= SLEEP_NANO,
+	     "Elapsed time test failed: expected >= %d "
+	     "nanoseconds, got %ld nanoseconds.\n",
+	     SLEEP_NANO, elapsed);
+}
+
+static void test_invalid_inputs(void)
+{
+	struct timespec ts = {1, 0};
+
+	// check EINVAL is returned when tv_nsec is too big
+	ts.tv_nsec = MAX_NSEC;
+	int retval = clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL);
+	TEST(retval == EINVAL,
+	     "Oversized tv_nsec test failed: Expected %s, got %s\n",
+	     strerror(EINVAL), strerror(retval));
+
+	// check EINVAL is returned when tv_nsec is too small
+	ts.tv_nsec = -1;
+	retval = clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL);
+	TEST(retval == EINVAL,
+	     "Undersized tv_nsec test failed: Expected %s, got %s\n",
+	     strerror(EINVAL), strerror(retval));
+
+	// check EINVAL is returned when given CPU_time clock of calling thread
+	retval = clock_nanosleep(CLOCK_THREAD_CPUTIME_ID, 0, &ts, NULL);
+	TEST(retval == EINVAL,
+	     "Calling threads cpu clock id test failed: Expected %s, got %s\n",
+	     strerror(EINVAL), strerror(retval));
+
+	const int unknown_clock = 123;
+
+	// check EINVAL is returned when given unknown clock
+	retval = clock_nanosleep(unknown_clock, 0, &ts, NULL);
+	TEST(retval == EINVAL,
+	     "Unknown clock id test failed: Expected %s, got %s\n",
+	     strerror(EINVAL), strerror(retval));
+
+	// check ENOTSUP is returned when given an unsupported clock
+	retval = clock_nanosleep(CLOCK_MONOTONIC_COARSE, 0, &ts, NULL);
+	TEST(retval == ENOTSUP,
+	     "Unsupported clock test failed. Expected %s, got %s\n",
+	     strerror(ENOTSUP), strerror(retval));
+}
+
+static void test_interupted_sleep()
+{
+	// check EINTR is returned when sleep is interupted by a signal handler
+	struct sigaction sa;
+	memset(&sa, 0, sizeof(sa));
+	sa.sa_handler = dummy_signal_handler;
+	sigaction(SIGALRM, &sa, NULL);
+
+	timer_t timerid = NULL;
+	if (timer_create(CLOCK_MONOTONIC, NULL, &timerid) == -1) {
+		t_error("timer_create() failed. Errno: %s\n", strerror(errno));
+		return;
+	}
+
+	struct itimerspec its = {
+	    .it_value.tv_sec = 0,
+	    .it_value.tv_nsec = SLEEP_NANO,
+	};
+	if (timer_settime(timerid, 0, &its, NULL) == -1) {
+		t_error("timer_settime() failed. Errno: %s\n", strerror(errno));
+		timer_delete(timerid);
+		return;
+	}
+
+	struct timespec ts = {2, 0};
+	struct timespec rmtp = {0, 0};
+	int retval = clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, &rmtp);
+
+	TEST(retval == EINTR, "Interupted sleep test failed: Expected %s, got %s\n",
+	     strerror(EINTR), strerror(retval));
+
+	// check that the remaining unslept time is returned into rmtp
+	TEST(rmtp.tv_sec > 0 || rmtp.tv_nsec > 0,
+	     "rmtp test failed: Expected value > 0, got rmpt.tv_sec = %lu, "
+	     "rmtp.tv_nsec = %lu\n",
+	     rmtp.tv_sec, rmtp.tv_nsec);
+
+	timer_delete(timerid);
+}
+
+int main(void)
+{
+	test_time_elapsed();
+	test_invalid_inputs();
+	test_interupted_sleep();
+	return t_status;
+}
-- 
2.34.1


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [musl] [PATCH 2/3 libc-test] functional:time:timer_delete test
  2024-11-05 22:44 [musl] [PATCH 1/3 libc-test] functional:time:clock_nanosleep test Ryan Gardner
@ 2024-11-05 22:44 ` Ryan Gardner
  2024-11-05 22:44 ` [musl] [PATCH 3/3 libc-test] functional:time:timer_settime test Ryan Gardner
  2024-11-06  7:38 ` [musl] [PATCH 1/3 libc-test] functional:time:clock_nanosleep test Szabolcs Nagy
  2 siblings, 0 replies; 4+ messages in thread
From: Ryan Gardner @ 2024-11-05 22:44 UTC (permalink / raw)
  To: musl; +Cc: Ryan Ward

From: Ryan Ward <ryan.c.ward3@boeing.com>

Testing of the musl API against the POSIX 2008 standard, which has
removed the requirement for returning EINVAL upon receiving an invalid
timer_id, and simply classifies it as undefined behavior.

Tests added:
- Deleting an unarmed timer
- Deleting an armed timer
- Deleting a timer that has been passed in the SIGEV_THREAD flag
- Deleting a completed timer
- Deleting, recreating, and deleting a timer

References:
- https://pubs.opengroup.org/onlinepubs/9699919799/functions/timer_delete.html

Signed-off-by: Ryan Ward <ryan.c.ward3@boeing.com>
---
 src/functional/timer_delete.c | 123 ++++++++++++++++++++++++++++++++++
 1 file changed, 123 insertions(+)
 create mode 100644 src/functional/timer_delete.c

diff --git a/src/functional/timer_delete.c b/src/functional/timer_delete.c
new file mode 100644
index 0000000..810a4d1
--- /dev/null
+++ b/src/functional/timer_delete.c
@@ -0,0 +1,123 @@
+/*
+ * timer_delete.c unit test
+ */
+#include "test.h"
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define TEST(c, ...) ((c) || (t_error(#c " failed: " __VA_ARGS__), 0))
+
+#define SLEEP_NANO 500000
+#define TIMER_SPEC \
+	(struct itimerspec) \
+	{ \
+		.it_value = {0, SLEEP_NANO}, .it_interval = { 0, 0 } \
+	}
+
+typedef struct {
+	clockid_t clock;
+	int armed;
+} timer_params;
+
+static void dummy_signal_handler(int signum) {}
+
+static void delete_timer(const timer_params *timer)
+{
+	timer_t timer_id = 0;
+	if (timer_create(timer->clock, NULL, &timer_id) < 0) {
+		t_error("Failed to created SIGEV_SIGNAL timer: %s\n", strerror(errno));
+		return;
+	}
+
+	if (timer->armed) {
+		if (timer_settime(timer_id, 0, &TIMER_SPEC, NULL) < 0) {
+			t_error("Failed to arm SIGEV_SIGNAL timer: %s\n", strerror(errno));
+		}
+	}
+
+	TEST(timer_delete(timer_id) == 0,
+	     "Failed to delete SIGEV_SIGNAL timer: %d\n", timer_id);
+}
+
+static void delete_thread_timer(const timer_params *timer)
+{
+	timer_t timer_id = 0;
+	struct sigevent sigev = {.sigev_signo = SIGRTMIN,
+	                         .sigev_notify = SIGEV_THREAD};
+
+	if (timer_create(timer->clock, &sigev, &timer_id) < 0) {
+		t_error("Failed to create SIGEV_THREAD timer: %s\n", strerror(errno));
+		return;
+	}
+
+	if (timer->armed) {
+		if (timer_settime(timer_id, 0, &TIMER_SPEC, NULL) < 0) {
+			t_error("Failed to arm SIGEV_THREAD timer: %s\n", strerror(errno));
+		}
+	}
+
+	TEST(timer_delete(timer_id) == 0,
+	     "Failed to delete SIGEV_THREAD timer: %d\n", timer_id);
+}
+
+static void delete_completed_timer(timer_t timer_id, clockid_t clock)
+{
+	struct sigaction sa = {.sa_handler = dummy_signal_handler};
+
+	sigemptyset(&sa.sa_mask);
+	if (sigaction(SIGALRM, &sa, NULL) == -1) {
+		t_error("Error setting up signal handler: %s\n", strerror(errno));
+		return;
+	}
+
+	if (timer_create(clock, NULL, &timer_id) < 0) {
+		t_error("Failed to created SIGEV_SIGNAL timer: %s\n", strerror(errno));
+		return;
+	}
+
+	if (timer_settime(timer_id, 0, &TIMER_SPEC, NULL) < 0) {
+		t_error("Failed to arm SIGEV_SIGNAL timer: %s\n", strerror(errno));
+	}
+
+	// Wait for SIGARLM
+	pause();
+
+	TEST(timer_delete(timer_id) == 0,
+	     "Failed to delete SIGEV_SIGNAL timer: %d\n", timer_id);
+}
+
+int main(void)
+{
+	static const clockid_t clocks_to_test[] = {CLOCK_REALTIME, CLOCK_MONOTONIC};
+	static const size_t num_clocks =
+	    sizeof(clocks_to_test) / sizeof(*clocks_to_test);
+
+	for (int clock = 0; clock < num_clocks; ++clock) {
+		const timer_params unarmed_timer = {0, clocks_to_test[clock]};
+		const timer_params armed_timer = {1, clocks_to_test[clock]};
+		// Delete an unarmed timer
+		delete_timer(&unarmed_timer);
+
+		// Delete an armed timer
+		delete_timer(&armed_timer);
+
+		// Delete a thread unarmed timer
+		delete_thread_timer(&unarmed_timer);
+
+		// Delete a thread armed timer
+		delete_thread_timer(&armed_timer);
+
+		// Let the timer run out, and delete
+		timer_t timer_id = 0;
+		delete_completed_timer(timer_id, clocks_to_test[clock]);
+
+		// Retry the same operation with the same timer_id (should succeed)
+		delete_completed_timer(timer_id, clocks_to_test[clock]);
+	}
+
+	return t_status;
+}
-- 
2.34.1


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [musl] [PATCH 3/3 libc-test] functional:time:timer_settime test
  2024-11-05 22:44 [musl] [PATCH 1/3 libc-test] functional:time:clock_nanosleep test Ryan Gardner
  2024-11-05 22:44 ` [musl] [PATCH 2/3 libc-test] functional:time:timer_delete test Ryan Gardner
@ 2024-11-05 22:44 ` Ryan Gardner
  2024-11-06  7:38 ` [musl] [PATCH 1/3 libc-test] functional:time:clock_nanosleep test Szabolcs Nagy
  2 siblings, 0 replies; 4+ messages in thread
From: Ryan Gardner @ 2024-11-05 22:44 UTC (permalink / raw)
  To: musl; +Cc: Ryan Gardner

Testing of musl API against POSIX 2008 standard.

Tests added:

- EINVAL is returned when value value struct is invalid
- setting an already armed timer results in a reset
- setting an already armed timer with it_value = 0 results
  in the timer being disarmed
- timer expires repeatedly when given non-zero interval value
- ovalue is set correctly

References:
- https://pubs.opengroup.org/onlinepubs/9699919799/functions/timer_settime.html

Signed-off-by: Ryan Gardner <ryan.p.gardner@boeing.com>
---
 src/functional/timer_settime.c | 212 +++++++++++++++++++++++++++++++++
 1 file changed, 212 insertions(+)
 create mode 100644 src/functional/timer_settime.c

diff --git a/src/functional/timer_settime.c b/src/functional/timer_settime.c
new file mode 100644
index 0000000..d5597fb
--- /dev/null
+++ b/src/functional/timer_settime.c
@@ -0,0 +1,212 @@
+/*
+ * timer_settime unit test
+ */
+
+#include "test.h"
+#include <errno.h>
+#include <signal.h>
+#include <string.h>
+#include <time.h>
+
+#define TEST(c, ...) ((c) || (t_error(#c " failed: " __VA_ARGS__), 0))
+#define TIME_NANO 500000
+#define OVERSIZED_NANOSECONDS 1000000001
+#define NUM_REPEATS 5
+
+volatile int timeout_count = 0;
+static void timeout_signal_counter(int signum) { timeout_count++; }
+
+static void test_settime(void)
+{
+	timer_t timerid = NULL;
+	if (timer_create(CLOCK_REALTIME, NULL, &timerid) == -1) {
+		t_error("Failed to create timer. Errno: %s\n", strerror(errno));
+		return;
+	}
+
+	struct itimerspec its = {
+	    .it_value = {1, TIME_NANO},
+	    .it_interval = {1, TIME_NANO},
+	};
+
+	// test timer_settime() returns 0 when successful
+	TEST(timer_settime(timerid, 0, &its, NULL) == 0,
+	     "Failed to set timer. Errno: %s\n", strerror(errno));
+
+	struct itimerspec res;
+	if (timer_gettime(timerid, &res) == -1) {
+		t_error("Failed to get timer time. Errno: %s\n", strerror(errno));
+		timer_delete(timerid);
+		return;
+	}
+
+	// test values from timer_settime() are set properly
+	TEST(res.it_value.tv_sec == 1 && res.it_value.tv_nsec != 0 &&
+	         res.it_interval.tv_sec == 1 &&
+	         res.it_interval.tv_nsec == TIME_NANO,
+	     "Failed to set correct timer values\n");
+
+	timer_delete(timerid);
+}
+
+static void test_periodic_timer(void)
+{
+	// setup signal handler
+	struct sigaction sa;
+	memset(&sa, 0, sizeof(sa));
+	sa.sa_handler = timeout_signal_counter;
+	sigaction(SIGALRM, &sa, NULL);
+
+	timer_t timerid = NULL;
+	if (timer_create(CLOCK_REALTIME, NULL, &timerid) == -1) {
+		t_error("Failed to create timer. Errno: %s\n", strerror(errno));
+		return;
+	}
+
+	struct itimerspec its = {
+	    .it_value = {0, TIME_NANO},
+	    .it_interval = {0, TIME_NANO},
+	};
+
+	TEST(timer_settime(timerid, 0, &its, NULL) == 0,
+	     "Failed to set timer. Errno: %s\n", strerror(errno));
+
+	struct timespec sleep_time = {
+	    .tv_sec = 1,
+	    .tv_nsec = 0,
+	};
+
+	// sleep for 1 second or until 5 timer timeouts have occured
+	while (
+	    clock_nanosleep(CLOCK_REALTIME, 0, &sleep_time, &sleep_time) == EINTR &&
+	    timeout_count < NUM_REPEATS) {
+		continue;
+	};
+
+	// test that the timer repeats after timeout
+	TEST(timeout_count >= NUM_REPEATS,
+	     "Failed periodic timer test. Expected timeout_count value >= 5, got "
+	     "%d\n",
+	     timeout_count);
+
+	timer_delete(timerid);
+}
+
+static void test_invalid_inputs(void)
+{
+	timer_t timerid = NULL;
+	if (timer_create(CLOCK_REALTIME, NULL, &timerid) == -1) {
+		t_error("Failed to create timer. Errno: %s\n", strerror(errno));
+		return;
+	}
+
+	struct itimerspec its = {
+	    .it_value = {0, OVERSIZED_NANOSECONDS},
+	    .it_interval = {0, 0},
+	};
+
+	TEST(timer_settime(timerid, 0, &its, NULL) == -1 && errno == EINVAL,
+	     "Oversized nanoseconds test failed, expected %s,got %s\n",
+	     strerror(EINVAL), strerror(errno));
+
+	errno = 0;
+	its.it_value.tv_nsec = -1;
+
+	TEST(timer_settime(timerid, 0, &its, NULL) == -1 && errno == EINVAL,
+	     "Undersized nanoseconds test failed, expected %s,got %s\n",
+	     strerror(EINVAL), strerror(errno));
+
+	timer_delete(timerid);
+}
+
+static void test_timer_resets(void)
+{
+	timer_t timerid = NULL;
+	if (timer_create(CLOCK_REALTIME, NULL, &timerid) == -1) {
+		t_error("Failed to create timer. Errno: %s\n", strerror(errno));
+		return;
+	}
+
+	struct itimerspec its = {
+	    .it_value = {0, TIME_NANO},
+	    .it_interval = {0, 0},
+	};
+
+	TEST(timer_settime(timerid, 0, &its, NULL) == 0,
+	     "Failed to set time. Errno: %s\n", strerror(errno));
+
+	its.it_value.tv_sec = 1;
+	its.it_value.tv_nsec = 0;
+
+	struct itimerspec ovalue = {.it_value.tv_nsec = 0};
+
+	TEST(timer_settime(timerid, 0, &its, &ovalue) == 0,
+	     "Failed to set time. Errno: %s\n", strerror(errno));
+
+	struct itimerspec res;
+	if (timer_gettime(timerid, &res) == -1) {
+		t_error("Failed to get timer time. Errno: %s\n", strerror(errno));
+		timer_delete(timerid);
+		return;
+	}
+
+	// check that it_value has been overwritten
+	TEST(res.it_value.tv_nsec > TIME_NANO,
+	     "Timer reset test failed, expected time until expiry > %d, got %ld\n",
+	     TIME_NANO, res.it_value.tv_nsec);
+
+	// check ovalue is set correctly
+	TEST(ovalue.it_value.tv_nsec != 0,
+	     "timer_settime() failed to set ovalue correctly\n");
+
+	timer_delete(timerid);
+}
+
+static void test_disarm_timer(void)
+{
+	timer_t timerid = NULL;
+	if (timer_create(CLOCK_REALTIME, NULL, &timerid) == -1) {
+		t_error("Failed to create timer. Errno: %s\n", strerror(errno));
+		return;
+	}
+
+	struct itimerspec its = {
+	    .it_value = {0, TIME_NANO},
+	    .it_interval = {0, 0},
+	};
+
+	TEST(timer_settime(timerid, 0, &its, NULL) == 0,
+	     "Failed to set time. Errno: %s\n", strerror(errno));
+
+	its.it_value.tv_nsec = 0;
+
+	TEST(timer_settime(timerid, 0, &its, NULL) == 0,
+	     "Failed to set time. Errno: %s\n", strerror(errno));
+
+	struct itimerspec res;
+
+	if (timer_gettime(timerid, &res) == -1) {
+		t_error("Failed to get timer time. Errno: %s\n", strerror(errno));
+		timer_delete(timerid);
+		return;
+	}
+
+	// setting the it_value of an armed timer to 0 should result in the timer
+	// being disarmed
+	TEST(res.it_value.tv_sec == 0 && res.it_value.tv_nsec == 0,
+	     "Disarm timer test failed, expected it_value.tv_sec = 0, got %ld, "
+	     "expected it_value.tv_nsec = 0, got %ld\n",
+	     res.it_value.tv_sec, res.it_value.tv_nsec);
+
+	timer_delete(timerid);
+}
+
+int main(void)
+{
+	test_settime();
+	test_periodic_timer();
+	test_invalid_inputs();
+	test_timer_resets();
+	test_disarm_timer();
+	return t_status;
+}
-- 
2.34.1


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [musl] [PATCH 1/3 libc-test] functional:time:clock_nanosleep test
  2024-11-05 22:44 [musl] [PATCH 1/3 libc-test] functional:time:clock_nanosleep test Ryan Gardner
  2024-11-05 22:44 ` [musl] [PATCH 2/3 libc-test] functional:time:timer_delete test Ryan Gardner
  2024-11-05 22:44 ` [musl] [PATCH 3/3 libc-test] functional:time:timer_settime test Ryan Gardner
@ 2024-11-06  7:38 ` Szabolcs Nagy
  2 siblings, 0 replies; 4+ messages in thread
From: Szabolcs Nagy @ 2024-11-06  7:38 UTC (permalink / raw)
  To: Ryan Gardner; +Cc: musl

* Ryan Gardner <ryan.p.gardner@boeing.com> [2024-11-05 22:44:35 +0000]:
> Testing of musl API against POSIX 2008 standard.
> 
> Tests added:
> 
> - EINTR is returned when sleep is interupted by a signal
> - ENOTSUP is returned when clock_id specifies an unsupported clock
> - EINVAL is returned when tv_nsec is out of range
> - EINVAL is returned when clock_id specifies an unknown clock

this is technically not futureproof as new clocks may be introduced
it's a common issue i dont have a good solution for.
but i think it's fine to pick an int for now.

> - EINVAL is returned when clokc_id specifies the CPU-time clock of calling thread
> - rmtp is set to the remaining unslept time when interupted.
> - time elapses as expected
> 
> References:
> - https://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_nanosleep.html
> 
> Signed-off-by: Ryan Gardner <ryan.p.gardner@boeing.com>

thanks
patches look good. but it will take a week or two
for me to be able to test and apply them.


^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2024-11-06  7:38 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-11-05 22:44 [musl] [PATCH 1/3 libc-test] functional:time:clock_nanosleep test Ryan Gardner
2024-11-05 22:44 ` [musl] [PATCH 2/3 libc-test] functional:time:timer_delete test Ryan Gardner
2024-11-05 22:44 ` [musl] [PATCH 3/3 libc-test] functional:time:timer_settime test Ryan Gardner
2024-11-06  7:38 ` [musl] [PATCH 1/3 libc-test] functional:time:clock_nanosleep test Szabolcs Nagy

Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/musl/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).