+
+
+
+/
+
+- The function is only available as fastcall function, so it may only
+be used in presence of a prototype.
+
- It is up to the caller to free the allocated memory block.
+
+
,
+[
+][
]
diff --git a/include/string.h b/include/string.h
index 3b7ece1d9..cf346894c 100644
--- a/include/string.h
+++ b/include/string.h
@@ -79,6 +79,7 @@ void* __fastcall__ __bzero (void* ptr, size_t n);
#if __CC65_STD__ == __CC65_STD_CC65__
void __fastcall__ bzero (void* ptr, size_t n); /* BSD */
char* __fastcall__ strdup (const char* s); /* SYSV/BSD */
+char* __fastcall__ strndup (const char* s, size_t maxlen); /* SYSV/BSD */
int __fastcall__ stricmp (const char* s1, const char* s2); /* DOS/Windows */
int __fastcall__ strcasecmp (const char* s1, const char* s2); /* Same for Unix */
char* __fastcall__ strcasestr (const char* str, const char* substr);
diff --git a/libsrc/common/strndup.s b/libsrc/common/strndup.s
new file mode 100644
index 000000000..ce81fc1cc
--- /dev/null
+++ b/libsrc/common/strndup.s
@@ -0,0 +1,56 @@
+;
+; Colin Leroy-Mira, 03.07.2025
+;
+; char* __fastcall__ strndup (const char* S, size_t maxlen);
+;
+
+ .importzp ptr4, c_sp
+ .import _strdup, _strlen, pushax, popax, _realloc
+
+ .export _strndup
+
+_strndup:
+ sta maxlen ; Remember maxlen
+ stx maxlen+1
+
+ jsr popax ; Duplicate string
+ jsr _strdup
+ jsr pushax ; Remember result
+
+ jsr _strlen ; Check length,
+ cpx maxlen+1
+ bcc out ; Return directly if < maxlen
+ bne :+
+ cmp maxlen
+ bcc out
+
+: ldy #$00 ; Otherwise, point to end of string,
+ lda maxlen
+ clc
+ adc (c_sp),y
+ sta ptr4
+ lda maxlen+1
+ iny
+ adc (c_sp),y
+ sta ptr4+1
+
+ dey ; Cut it short,
+ tya
+ sta (ptr4),y
+
+ ldx maxlen+1 ; And finally, realloc to maxlen+1
+ ldy maxlen
+ iny
+ tya
+ bne :+
+ inx
+: jsr _realloc ; TOS still contains result
+ ; We consider realloc will not fail,
+ ; as the block shrinks.
+ jsr pushax ; push/pop for size optimisation
+out:
+ jmp popax
+
+ .bss
+
+maxlen: .res 2
diff --git a/test/val/lib_common_strlen.c b/test/val/lib_common_strlen.c
new file mode 100644
index 000000000..ec79c025a
--- /dev/null
+++ b/test/val/lib_common_strlen.c
@@ -0,0 +1,22 @@
+#include
+#include "unittest.h"
+
+#define SHORT_STR "abcdefghijklmnopqrstuvwxyz"
+
+#define MID_STR_LEN 700 /* Two pages and something */
+TEST
+{
+ char *src;
+ int i;
+
+ /* Long enough for the whole string */
+ ASSERT_IsTrue(strlen("") == 0, "strlen(\"\") != 0");
+ ASSERT_IsTrue(strlen(SHORT_STR) == 26, "strlen(\""SHORT_STR"\") != 26");
+
+ src = malloc(MID_STR_LEN+1);
+ ASSERT_IsTrue(src != NULL, "Could not allocate source string");
+ memset(src, 'a', MID_STR_LEN-1);
+ src[MID_STR_LEN] = '\0';
+ ASSERT_IsTrue(strlen(src) == MID_STR_LEN, "strlen(\"700 chars\") != 700");
+}
+ENDTEST
diff --git a/test/val/lib_common_strndup.c b/test/val/lib_common_strndup.c
new file mode 100644
index 000000000..5b3859b37
--- /dev/null
+++ b/test/val/lib_common_strndup.c
@@ -0,0 +1,60 @@
+#include
+#include "unittest.h"
+
+#define SHORT_STR "abcdefghijklmnopqrstuvwxyz"
+
+#define MID_STR_LEN 700 /* Two pages and something */
+#define LONG_STR_LEN 40000UL /* Two long to duplicate */
+TEST
+{
+ char *dst;
+ char *src;
+ int i;
+
+ dst = strndup("", 0);
+ ASSERT_IsTrue(dst != NULL, "strndup returned NULL")
+ ASSERT_IsTrue(!strcmp(dst, ""), "strings differ");
+ free(dst);
+
+ for (i = 0; i < 30; i+=10) {
+ dst = strndup(SHORT_STR, i);
+ ASSERT_IsTrue(dst != NULL, "strndup returned NULL");
+ printf("strlen %s = %d (%d expected)\n", dst, strlen(dst), i);
+ ASSERT_IsTrue(strlen(dst) == i, "string lengths differ");
+ ASSERT_IsTrue(!strncmp(dst, SHORT_STR, i), "strings differ");
+ free(dst);
+ }
+
+ dst = strndup(SHORT_STR, 50);
+ ASSERT_IsTrue(dst != NULL, "strndup returned NULL");
+ printf("strlen %s = %d (%d expected)\n", dst, strlen(dst), i);
+ ASSERT_IsTrue(strlen(dst) == 26, "string lengths differ");
+ ASSERT_IsTrue(!strcmp(dst, SHORT_STR), "strings differ");
+ free(dst);
+
+
+ src = malloc(MID_STR_LEN+1);
+ ASSERT_IsTrue(src != NULL, "Could not allocate source string");
+ memset(src, 'a', MID_STR_LEN);
+ src[MID_STR_LEN] = '\0';
+
+ for (i = 0; i < MID_STR_LEN -1; i+=10) {
+ dst = strndup(src, i);
+ ASSERT_IsTrue(dst != NULL, "strndup returned NULL");
+ printf("strlen %s = %d (%d expected)\n", dst, strlen(dst), i);
+ ASSERT_IsTrue(strlen(dst) == i, "string lengths differ");
+ ASSERT_IsTrue(!strncmp(dst, src, i), "strings differ");
+ free(dst);
+ }
+
+ for (i = MID_STR_LEN; i < MID_STR_LEN * 2; i+=10) {
+ dst = strndup(src, i);
+ ASSERT_IsTrue(dst != NULL, "strndup returned NULL");
+ printf("%d, strlen %s = %d (%d expected)\n", i, dst, strlen(dst), strlen(src));
+ ASSERT_IsTrue(strlen(dst) == strlen(src), "string lengths differ");
+ ASSERT_IsTrue(!strcmp(dst, src), "strings differ");
+ free(dst);
+ }
+
+}
+ENDTEST
diff --git a/test/val/lib_common_strnlen.c b/test/val/lib_common_strnlen.c
new file mode 100644
index 000000000..892bdffa1
--- /dev/null
+++ b/test/val/lib_common_strnlen.c
@@ -0,0 +1,29 @@
+#include
+#include "unittest.h"
+
+#define SHORT_STR "abcdefghijklmnopqrstuvwxyz"
+
+#define MID_STR_LEN 700 /* Two pages and something */
+TEST
+{
+ char *src;
+ int i;
+
+ /* Long enough for the whole string */
+ ASSERT_IsTrue(strnlen("", 0) == 0, "strnlen(\"\", 0) != 0");
+ ASSERT_IsTrue(strnlen("", 10) == 0, "strnlen(\"\", 10) != 0");
+ ASSERT_IsTrue(strnlen(SHORT_STR, 0) == 0, "strnlen(\""SHORT_STR"\", 0) != 0");
+ ASSERT_IsTrue(strnlen(SHORT_STR, 10) == 10, "strnlen(\""SHORT_STR"\", 10) != 10");
+ ASSERT_IsTrue(strnlen(SHORT_STR, 26) == 26, "strnlen(\""SHORT_STR"\", 26) != 26");
+ ASSERT_IsTrue(strnlen(SHORT_STR, 50) == 26, "strnlen(\""SHORT_STR"\", 50) != 26");
+
+ src = malloc(MID_STR_LEN+1);
+ ASSERT_IsTrue(src != NULL, "Could not allocate source string");
+ memset(src, 'a', MID_STR_LEN-1);
+ src[MID_STR_LEN] = '\0';
+ ASSERT_IsTrue(strnlen(src, 0) == 0, "strnlen(src, 0) != 0");
+ ASSERT_IsTrue(strnlen(src, 10) == 10, "strnlen(src, 10) != 10");
+ ASSERT_IsTrue(strnlen(src, 260) == 260, "strnlen(src, 260) != 260");
+ ASSERT_IsTrue(strnlen(src, MID_STR_LEN+1) == MID_STR_LEN, "strnlen(src, MID_STR_LEN+1) != MID_STR_LEN");
+}
+ENDTEST