diff --git a/localedata/Makefile b/localedata/Makefile
index e89bacc1aa..ccfcb0049f 100644
--- a/localedata/Makefile
+++ b/localedata/Makefile
@@ -167,6 +167,8 @@ tests-special += $(objpfx)mtrace-tst-leaks.out
endif
endif
endif
+tests-container = \
+ tst-localedef-hardlinks
# Files to install.
ifeq ($(INSTALL_UNCOMPRESSED),yes)
diff --git a/localedata/tst-localedef-hardlinks.c b/localedata/tst-localedef-hardlinks.c
new file mode 100644
index 0000000000..e8fda167be
--- /dev/null
+++ b/localedata/tst-localedef-hardlinks.c
@@ -0,0 +1,135 @@
+/* Test --no-hard-links option to localedef.
+ Copyright (C) 2020 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ . */
+
+/* The test is designed to run in a container and execute localedef
+ once without --no-hard-links, verify that there are 2 hard links to
+ LC_CTYPE, and then run again *with* --no-hard-links and verify there
+ are no hard links and link counts remain at 1. The expectation here
+ is that LC_CTYPE is identical for both locales because they are same
+ empty locale but with a different name. We use tests-container in
+ this test because the hard link optimziation is only carried out for
+ the default locale installation directory, and to write to that we
+ need write access to that directory, enabled by 'su' via
+ tests-container framework. */
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+/* Each test compiles a locale to output, and has an expected link count
+ for LC_CTYPE. This test expects that localedef removes the existing
+ files before installing new copies of the files, and we do not
+ cleanup between localedef runs. We can't cleanup between each pair
+ of runs since localedef must see the existing locale in order to
+ determine that space could be saved by using a hardlink. */
+struct test_data
+{
+ /* Arguments to localedef for this step. */
+ const char * argv[16];
+ /* Expected output file generated by running localedef. */
+ const char *output;
+ /* Expected st_nlink count for the output. */
+ int st_nlink;
+};
+
+/* Check for link count. */
+void
+check_link (struct test_data step)
+{
+ struct stat64 locale;
+ char *output;
+
+ output = xasprintf ("%s/%s", support_complocaledir_prefix, step.output);
+ xstat (output, &locale);
+ free (output);
+ TEST_COMPARE (locale.st_nlink, step.st_nlink);
+}
+
+static void
+run_localedef (void *step)
+{
+ const char *prog = xasprintf ("%s/localedef", support_bindir_prefix);
+ struct test_data *one = (struct test_data *) step;
+
+ one->argv[0] = prog;
+ execv (prog, (char * const *) one->argv);
+ FAIL_EXIT1 ("execv: %m");
+}
+
+#define TEST1DIR "test1_locale.dir"
+#define TEST2DIR "test2_locale.dir"
+
+/* The whole test has 4 steps described below. Note the argv[0] NULL
+ will be filled in at runtime by run_localedef. */
+static struct test_data step[4] = {
+ { .argv = { NULL, "--no-archive", "-i", "/test1_locale", TEST1DIR, NULL },
+ .output = TEST1DIR "/LC_CTYPE",
+ .st_nlink = 1 },
+ { .argv = { NULL, "--no-archive", "-i", "/test2_locale", TEST2DIR, NULL },
+ .output = TEST2DIR "/LC_CTYPE",
+ .st_nlink = 2 },
+ { .argv = { NULL, "--no-archive", "--no-hard-links", "-i", "/test1_locale",
+ TEST1DIR, NULL },
+ .output = TEST1DIR "/LC_CTYPE",
+ .st_nlink = 1 },
+ { .argv = { NULL, "--no-archive", "--no-hard-links", "-i", "/test2_locale",
+ TEST1DIR, NULL },
+ .output = TEST2DIR "/LC_CTYPE",
+ .st_nlink = 1 },
+};
+
+static int
+do_test (void)
+{
+ struct support_capture_subprocess result;
+
+ printf ("INFO: $complocaledir is %s\n", support_complocaledir_prefix);
+ /* Compile the first locale. */
+ result = support_capture_subprocess (run_localedef, (void *) &step[0]);
+ support_capture_subprocess_check (&result, "execv", 1, sc_allow_stderr);
+ check_link (step[0]);
+
+ /* This time around we should have link counts of 2 for the second
+ linked locale since categories are identical. */
+ result = support_capture_subprocess (run_localedef, (void *) &step[1]);
+ support_capture_subprocess_check (&result, "execv", 1, sc_allow_stderr);
+ check_link (step[1]);
+
+ /* Again with --no-hard-links (link count is always one). */
+ result = support_capture_subprocess (run_localedef, (void *) &step[2]);
+ support_capture_subprocess_check (&result, "execv", 1, sc_allow_stderr);
+ check_link (step[2]);
+
+ /* Again with --no-hard-links, and the link count must remain 1. */
+ result = support_capture_subprocess (run_localedef, (void *) &step[3]);
+ support_capture_subprocess_check (&result, "execv", 1, sc_allow_stderr);
+ check_link (step[3]);
+
+ /* Tested without and with --no-hard-links and link counts were
+ consistent. */
+ return EXIT_SUCCESS;
+}
+
+#include
diff --git a/localedata/tst-localedef-hardlinks.root/postclean.req b/localedata/tst-localedef-hardlinks.root/postclean.req
new file mode 100644
index 0000000000..48f3c4bd4e
--- /dev/null
+++ b/localedata/tst-localedef-hardlinks.root/postclean.req
@@ -0,0 +1,2 @@
+# We do not want test locales to remain installed for future tests so
+# we request that the testroot be cleaned after the test is complete.
diff --git a/localedata/tst-localedef-hardlinks.root/test1_locale b/localedata/tst-localedef-hardlinks.root/test1_locale
new file mode 100644
index 0000000000..79f4c3aec4
--- /dev/null
+++ b/localedata/tst-localedef-hardlinks.root/test1_locale
@@ -0,0 +1,3 @@
+comment_char %
+escape_char /
+% Empty test locale. Must be identical to the other test locale.
diff --git a/localedata/tst-localedef-hardlinks.root/test2_locale b/localedata/tst-localedef-hardlinks.root/test2_locale
new file mode 100644
index 0000000000..79f4c3aec4
--- /dev/null
+++ b/localedata/tst-localedef-hardlinks.root/test2_locale
@@ -0,0 +1,3 @@
+comment_char %
+escape_char /
+% Empty test locale. Must be identical to the other test locale.
diff --git a/localedata/tst-localedef-hardlinks.root/tst-localedef-hardlinks.script b/localedata/tst-localedef-hardlinks.root/tst-localedef-hardlinks.script
new file mode 100644
index 0000000000..f0eccec251
--- /dev/null
+++ b/localedata/tst-localedef-hardlinks.root/tst-localedef-hardlinks.script
@@ -0,0 +1,9 @@
+# Need root to write to $complocaledir when running localedef.
+su
+
+# The container install ensures we have uncompressed charmaps so we don't
+# need gzip, and we don't need to copy in the uncompressed charmap from
+# the source tree.
+
+# The container install ensures we have the compiled locale dirctory
+# present for localedef to write to by default.