other misbehavior due to the missing termination. It also NUL-pads
 the destination buffer if the source contents are shorter than the
 destination buffer size, which may be a needless performance penalty
-for callers using only NUL-terminated strings. The safe replacement is
+for callers using only NUL-terminated strings.
+
+When the destination is required to be NUL-terminated, the replacement is
 strscpy(), though care must be given to any cases where the return value
 of strncpy() was used, since strscpy() does not return a pointer to the
 destination, but rather a count of non-NUL bytes copied (or negative
 errno when it truncates). Any cases still needing NUL-padding should
 instead use strscpy_pad().
 
-If a caller is using non-NUL-terminated strings, strncpy() can
-still be used, but destinations should be marked with the `__nonstring
+If a caller is using non-NUL-terminated strings, strtomem() should be
+used, and the destinations should be marked with the `__nonstring
 <https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html>`_
-attribute to avoid future compiler warnings.
+attribute to avoid future compiler warnings. For cases still needing
+NUL-padding, strtomem_pad() can be used.
 
 strlcpy()
 ---------
 
 #define POS    __pass_object_size(1)
 #define POS0   __pass_object_size(0)
 
+/**
+ * strncpy - Copy a string to memory with non-guaranteed NUL padding
+ *
+ * @p: pointer to destination of copy
+ * @q: pointer to NUL-terminated source string to copy
+ * @size: bytes to write at @p
+ *
+ * If strlen(@q) >= @size, the copy of @q will stop after @size bytes,
+ * and @p will NOT be NUL-terminated
+ *
+ * If strlen(@q) < @size, following the copy of @q, trailing NUL bytes
+ * will be written to @p until @size total bytes have been written.
+ *
+ * Do not use this function. While FORTIFY_SOURCE tries to avoid
+ * over-reads of @q, it cannot defend against writing unterminated
+ * results to @p. Using strncpy() remains ambiguous and fragile.
+ * Instead, please choose an alternative, so that the expectation
+ * of @p's contents is unambiguous:
+ *
+ * +--------------------+-----------------+------------+
+ * | @p needs to be:    | padded to @size | not padded |
+ * +====================+=================+============+
+ * |     NUL-terminated | strscpy_pad()   | strscpy()  |
+ * +--------------------+-----------------+------------+
+ * | not NUL-terminated | strtomem_pad()  | strtomem() |
+ * +--------------------+-----------------+------------+
+ *
+ * Note strscpy*()'s differing return values for detecting truncation,
+ * and strtomem*()'s expectation that the destination is marked with
+ * __nonstring when it is a character array.
+ *
+ */
 __FORTIFY_INLINE __diagnose_as(__builtin_strncpy, 1, 2, 3)
 char *strncpy(char * const POS p, const char *q, __kernel_size_t size)
 {
 
 void memcpy_and_pad(void *dest, size_t dest_len, const void *src, size_t count,
                    int pad);
 
+/**
+ * strtomem_pad - Copy NUL-terminated string to non-NUL-terminated buffer
+ *
+ * @dest: Pointer of destination character array (marked as __nonstring)
+ * @src: Pointer to NUL-terminated string
+ * @pad: Padding character to fill any remaining bytes of @dest after copy
+ *
+ * This is a replacement for strncpy() uses where the destination is not
+ * a NUL-terminated string, but with bounds checking on the source size, and
+ * an explicit padding character. If padding is not required, use strtomem().
+ *
+ * Note that the size of @dest is not an argument, as the length of @dest
+ * must be discoverable by the compiler.
+ */
+#define strtomem_pad(dest, src, pad)   do {                            \
+       const size_t _dest_len = __builtin_object_size(dest, 1);        \
+                                                                       \
+       BUILD_BUG_ON(!__builtin_constant_p(_dest_len) ||                \
+                    _dest_len == (size_t)-1);                          \
+       memcpy_and_pad(dest, _dest_len, src, strnlen(src, _dest_len), pad); \
+} while (0)
+
+/**
+ * strtomem - Copy NUL-terminated string to non-NUL-terminated buffer
+ *
+ * @dest: Pointer of destination character array (marked as __nonstring)
+ * @src: Pointer to NUL-terminated string
+ *
+ * This is a replacement for strncpy() uses where the destination is not
+ * a NUL-terminated string, but with bounds checking on the source size, and
+ * without trailing padding. If padding is required, use strtomem_pad().
+ *
+ * Note that the size of @dest is not an argument, as the length of @dest
+ * must be discoverable by the compiler.
+ */
+#define strtomem(dest, src)    do {                                    \
+       const size_t _dest_len = __builtin_object_size(dest, 1);        \
+                                                                       \
+       BUILD_BUG_ON(!__builtin_constant_p(_dest_len) ||                \
+                    _dest_len == (size_t)-1);                          \
+       memcpy(dest, src, min(_dest_len, strnlen(src, _dest_len)));     \
+} while (0)
+
 /**
  * memset_after - Set a value after a struct member to the end of a struct
  *
 
 };
 
 #define check(instance, v) do {        \
-       int i;  \
        BUILD_BUG_ON(sizeof(instance.data) != 32);      \
-       for (i = 0; i < sizeof(instance.data); i++) {   \
+       for (size_t i = 0; i < sizeof(instance.data); i++) {    \
                KUNIT_ASSERT_EQ_MSG(test, instance.data[i], v, \
                        "line %d: '%s' not initialized to 0x%02x @ %d (saw 0x%02x)\n", \
                        __LINE__, #instance, v, i, instance.data[i]);   \
 } while (0)
 
 #define compare(name, one, two) do { \
-       int i; \
        BUILD_BUG_ON(sizeof(one) != sizeof(two)); \
-       for (i = 0; i < sizeof(one); i++) {     \
+       for (size_t i = 0; i < sizeof(one); i++) {      \
                KUNIT_EXPECT_EQ_MSG(test, one.data[i], two.data[i], \
                        "line %d: %s.data[%d] (0x%02x) != %s.data[%d] (0x%02x)\n", \
                        __LINE__, #one, i, one.data[i], #two, i, two.data[i]); \
 #undef TEST_OP
 }
 
+static void strtomem_test(struct kunit *test)
+{
+       static const char input[] = "hi";
+       static const char truncate[] = "this is too long";
+       struct {
+               unsigned long canary1;
+               unsigned char output[sizeof(unsigned long)] __nonstring;
+               unsigned long canary2;
+       } wrap;
+
+       memset(&wrap, 0xFF, sizeof(wrap));
+       KUNIT_EXPECT_EQ_MSG(test, wrap.canary1, ULONG_MAX,
+                           "bad initial canary value");
+       KUNIT_EXPECT_EQ_MSG(test, wrap.canary2, ULONG_MAX,
+                           "bad initial canary value");
+
+       /* Check unpadded copy leaves surroundings untouched. */
+       strtomem(wrap.output, input);
+       KUNIT_EXPECT_EQ(test, wrap.canary1, ULONG_MAX);
+       KUNIT_EXPECT_EQ(test, wrap.output[0], input[0]);
+       KUNIT_EXPECT_EQ(test, wrap.output[1], input[1]);
+       for (size_t i = 2; i < sizeof(wrap.output); i++)
+               KUNIT_EXPECT_EQ(test, wrap.output[i], 0xFF);
+       KUNIT_EXPECT_EQ(test, wrap.canary2, ULONG_MAX);
+
+       /* Check truncated copy leaves surroundings untouched. */
+       memset(&wrap, 0xFF, sizeof(wrap));
+       strtomem(wrap.output, truncate);
+       KUNIT_EXPECT_EQ(test, wrap.canary1, ULONG_MAX);
+       for (size_t i = 0; i < sizeof(wrap.output); i++)
+               KUNIT_EXPECT_EQ(test, wrap.output[i], truncate[i]);
+       KUNIT_EXPECT_EQ(test, wrap.canary2, ULONG_MAX);
+
+       /* Check padded copy leaves only string padded. */
+       memset(&wrap, 0xFF, sizeof(wrap));
+       strtomem_pad(wrap.output, input, 0xAA);
+       KUNIT_EXPECT_EQ(test, wrap.canary1, ULONG_MAX);
+       KUNIT_EXPECT_EQ(test, wrap.output[0], input[0]);
+       KUNIT_EXPECT_EQ(test, wrap.output[1], input[1]);
+       for (size_t i = 2; i < sizeof(wrap.output); i++)
+               KUNIT_EXPECT_EQ(test, wrap.output[i], 0xAA);
+       KUNIT_EXPECT_EQ(test, wrap.canary2, ULONG_MAX);
+
+       /* Check truncated padded copy has no padding. */
+       memset(&wrap, 0xFF, sizeof(wrap));
+       strtomem(wrap.output, truncate);
+       KUNIT_EXPECT_EQ(test, wrap.canary1, ULONG_MAX);
+       for (size_t i = 0; i < sizeof(wrap.output); i++)
+               KUNIT_EXPECT_EQ(test, wrap.output[i], truncate[i]);
+       KUNIT_EXPECT_EQ(test, wrap.canary2, ULONG_MAX);
+}
+
 static struct kunit_case memcpy_test_cases[] = {
        KUNIT_CASE(memset_test),
        KUNIT_CASE(memcpy_test),
        KUNIT_CASE(memmove_test),
+       KUNIT_CASE(strtomem_test),
        {}
 };