aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias Andrée <m@maandree.se>2026-02-28 19:17:45 +0100
committerMattias Andrée <m@maandree.se>2026-02-28 19:17:45 +0100
commit7c545b4b9c31dbfb2e8430aded030654a92e967a (patch)
treecd79fb055bfcca79dee8b1b35c0ed59ea3a0c85a
downloadlibabort-7c545b4b9c31dbfb2e8430aded030654a92e967a.tar.gz
libabort-7c545b4b9c31dbfb2e8430aded030654a92e967a.tar.bz2
libabort-7c545b4b9c31dbfb2e8430aded030654a92e967a.tar.xz
First commitHEAD1.0master
Signed-off-by: Mattias Andrée <m@maandree.se>
-rw-r--r--.gitignore16
-rw-r--r--LICENSE15
-rw-r--r--Makefile110
-rw-r--r--README48
-rw-r--r--common.h86
-rw-r--r--config.mk8
-rw-r--r--libabort.759
-rw-r--r--libabort.h233
-rw-r--r--libabort_saprintf.358
-rw-r--r--libabort_saprintf.c28
-rw-r--r--libabort_stpacat.361
-rw-r--r--libabort_stpacat.c30
-rw-r--r--libabort_stpacpy.356
-rw-r--r--libabort_stpacpy.c27
-rw-r--r--libabort_stracat.362
-rw-r--r--libabort_stracat.c33
-rw-r--r--libabort_stracpy.356
-rw-r--r--libabort_stracpy.c28
-rw-r--r--libabort_stralen.353
-rw-r--r--libabort_stralen.c34
-rw-r--r--libabort_vsaprintf.358
-rw-r--r--libabort_vsaprintf.c11
-rw-r--r--mk/linux.mk6
-rw-r--r--mk/macos.mk6
-rw-r--r--mk/windows.mk6
l---------saprintf.3libabort1
l---------stpacat.3libabort1
l---------stpacpy.3libabort1
l---------stracat.3libabort1
l---------stracpy.3libabort1
l---------stralen.3libabort1
l---------vsaprintf.3libabort1
32 files changed, 1195 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bb11397
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+*\#*
+*~
+*.o
+*.a
+*.t
+*.lo
+*.to
+*.su
+*.so
+*.so.*
+*.dll
+*.dylib
+*.gch
+*.gcov
+*.gcno
+*.gcda
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..1634eae
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+© 2026 Mattias Andrée <m@maandree.se>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..8619f7e
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,110 @@
+.POSIX:
+
+CONFIGFILE = config.mk
+include $(CONFIGFILE)
+
+OS = linux
+# Linux: linux
+# Mac OS: macos
+# Windows: windows
+include mk/$(OS).mk
+
+
+LIB_MAJOR = 1
+LIB_MINOR = 0
+LIB_VERSION = $(LIB_MAJOR).$(LIB_MINOR)
+LIB_NAME = abort
+
+
+OBJ =\
+ libabort_stracpy.o\
+ libabort_stpacpy.o\
+ libabort_stracat.o\
+ libabort_stralen.o\
+ libabort_stpacat.o\
+ libabort_saprintf.o\
+ libabort_vsaprintf.o
+
+HDR =\
+ libabort.h\
+ common.h
+
+MAN3 = $(OBJ:.o=.3)\
+ stracpy.3libabort\
+ saprintf.3libabort\
+ vsaprintf.3libabort\
+ stpacpy.3libabort\
+ stracat.3libabort\
+ stralen.3libabort\
+ stpacat.3libabort
+
+MAN7 = libabort.7
+
+LOBJ = $(OBJ:.o=.lo)
+TOBJ = $(OBJ:.o=.to)
+TEST = $(OBJ:.o=.t)
+
+all: libabort.a libabort.$(LIBEXT) $(TEST)
+$(OBJ): $(HDR)
+$(TOBJ): $(HDR)
+$(LOBJ): $(HDR)
+$(TEST): libabort.a
+
+.c.o:
+ $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
+
+.c.lo:
+ $(CC) -fPIC -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
+
+.c.to:
+ $(CC) -DTEST -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
+
+.to.t:
+ $(CC) -o $@ $< libabort.a $(LDPPFLAGS)
+
+libabort.a: $(OBJ)
+ @rm -f -- $@
+ $(AR) rc $@ $(OBJ)
+ $(AR) ts $@ > /dev/null
+
+libabort.$(LIBEXT): $(LOBJ)
+ $(CC) $(LIBFLAGS) -o $@ $(LOBJ) $(LDFLAGS)
+
+check: $(TEST)
+ @set -e;\
+ for t in $(TEST); do\
+ printf '%s\n' ./$$t;\
+ $(CHECK_PREFIX) ./$$t;\
+ done
+
+install: libabort.a libabort.$(LIBEXT)
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/lib"
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/include"
+ mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man3"
+ mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man7"
+ cp -- libabort.a "$(DESTDIR)$(PREFIX)/lib/"
+ cp -- libabort.$(LIBEXT) "$(DESTDIR)$(PREFIX)/lib/libabort.$(LIBMINOREXT)"
+ $(FIX_INSTALL_NAME) "$(DESTDIR)$(PREFIX)/lib/libabort.$(LIBMINOREXT)"
+ ln -sf -- libabort.$(LIBMINOREXT) "$(DESTDIR)$(PREFIX)/lib/libabort.$(LIBMAJOREXT)"
+ ln -sf -- libabort.$(LIBMAJOREXT) "$(DESTDIR)$(PREFIX)/lib/libabort.$(LIBEXT)"
+ cp -- libabort.h "$(DESTDIR)$(PREFIX)/include/"
+ cp -P -- $(MAN3) "$(DESTDIR)$(MANPREFIX)/man3/"
+ cp -P -- $(MAN7) "$(DESTDIR)$(MANPREFIX)/man7/"
+
+uninstall:
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libabort.a"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libabort.$(LIBMAJOREXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libabort.$(LIBMINOREXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libabort.$(LIBEXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/include/libabort.h"
+ -cd -- "$(DESTDIR)$(MANPREFIX)/man3/" && rm -f -- $(MAN3)
+ -cd -- "$(DESTDIR)$(MANPREFIX)/man7/" && rm -f -- $(MAN7)
+
+clean:
+ -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.dll *.dylib *.to *.t
+ -rm -f -- *.gch *.gcov *.gcno *.gcda *.$(LIBEXT)
+
+.SUFFIXES:
+.SUFFIXES: .lo .o .c .t .to
+
+.PHONY: all check install uninstall clean
diff --git a/README b/README
new file mode 100644
index 0000000..95bf2d7
--- /dev/null
+++ b/README
@@ -0,0 +1,48 @@
+NAME
+ libabort - String functions that abort when getting out of bounds
+
+SYNOPSIS
+ #include <libabort.h>
+
+ Link with -labort.
+
+DESCRIPTION
+ libabort provides string functions that call abort(3) rather than
+ silently truncating or read or write out of bounds.
+
+ Each function is namespaced with the prefix libabort_, and shorthand
+ names are available without this prefix, however defining
+ LIBABORT_NO_SHORTHANDS before including <libabort.h> will cause these
+ shorthands not to be defined.
+
+ The following functions are provided:
+
+ libabort_stracpy(3)
+ Copy a NUL terminated string into a fixed-size buffer.
+ Returns the written string.
+
+ libabort_stpacpy(3)
+ Copy a NUL terminated string into a fixed-size buffer.
+ Returns the end of the written string.
+
+ libabort_stracat(3)
+ Append a NUL terminated string to a NUL terminated string within
+ a fixed-size buffer. Returns the augmented string.
+
+ libabort_stpacat(3)
+ Append a NUL terminated string to a NUL terminated string within
+ a fixed-size buffer. Returns the end of the augmented string.
+
+ libabort_stralen(3)
+ Compute the length of string that is expected to be NUL
+ terminated.
+
+ libabort_saprintf(3)
+ Format a string into a fixed-size buffer.
+
+ libabort_vsaprintf(3)
+ Format a string into a fixed-size buffer using a va_list
+ argument list.
+
+SEE ALSO
+ None.
diff --git a/common.h b/common.h
new file mode 100644
index 0000000..3e34f3b
--- /dev/null
+++ b/common.h
@@ -0,0 +1,86 @@
+/* See LICENSE file for copyright and license details. */
+#include "libabort.h"
+
+#if defined(__GNUC__)
+# define CONST __attribute__((__const__))
+#else
+# define CONST
+#endif
+
+#ifdef TEST
+# ifdef __linux__
+# include <sys/prctl.h>
+# endif
+# include <sys/resource.h>
+# include <sys/types.h>
+# include <sys/wait.h>
+# include <signal.h>
+# include <string.h>
+# include <unistd.h>
+
+
+# if defined(PR_SET_DUMPABLE)
+# define INIT_TEST_ABORT()\
+ do {\
+ struct rlimit rl__;\
+ rl__.rlim_cur = 0;\
+ rl__.rlim_max = 0;\
+ (void) setrlimit(RLIMIT_CORE, &rl__);\
+ (void) prctl(PR_SET_DUMPABLE, 0);\
+ EXPECT_ABORT(abort());\
+ } while (0)
+# else
+# define INIT_TEST_ABORT()\
+ do {\
+ struct rlimit rl__;\
+ rl__.rlim_cur = 0;\
+ rl__.rlim_max = 0;\
+ (void) setrlimit(RLIMIT_CORE, &rl__);\
+ EXPECT_ABORT(abort());\
+ } while (0)
+# endif
+
+# define EXPECT__(EXPR, HOW, RETEXTRACT, RETEXPECT)\
+ do {\
+ pid_t pid__;\
+ int status__;\
+ pid__ = fork();\
+ EXPECT(pid__ != -1);\
+ if (pid__ == 0) {\
+ (EXPR);\
+ _exit(0);\
+ }\
+ EXPECT(waitpid(pid__, &status__, 0) == pid__);\
+ EXPECT(HOW(status__));\
+ EXPECT(RETEXTRACT(status__) == RETEXPECT);\
+ } while (0)
+
+# define EXPECT_ABORT(EXPR)\
+ do {\
+ EXPECT__(EXPR, WIFSIGNALED, WTERMSIG, SIGABRT);\
+ } while (0)
+
+# define EXPECT_NO_ABORT(EXPR)\
+ do {\
+ EXPECT__(EXPR, WIFEXITED, WEXITSTATUS, 0);\
+ (EXPR);\
+ } while (0)
+
+# define EXPECT(EXPR)\
+ do {\
+ if (!(EXPR)) {\
+ fprintf(stderr, "Failure at %s:%d: %s\n", __FILE__, __LINE__, #EXPR);\
+ exit(1);\
+ }\
+ } while (0)
+
+# define TESTED\
+ CONST int\
+ main(void)\
+ {\
+ return 0;\
+ }
+
+extern volatile size_t test_size_t_discard;
+
+#endif
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..f4adf12
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,8 @@
+PREFIX = /usr
+MANPREFIX = $(PREFIX)/share/man
+
+CC = c99
+
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE
+CFLAGS =
+LDFLAGS =
diff --git a/libabort.7 b/libabort.7
new file mode 100644
index 0000000..4686be3
--- /dev/null
+++ b/libabort.7
@@ -0,0 +1,59 @@
+.TH LIBABORT 7 LIBABORT
+.SH NAME
+libabort \- String functions that abort when getting out of bounds
+
+.SH SYNOPSIS
+.nf
+#include <libabort.h>
+.fi
+.PP
+Link with
+.IR -labort .
+
+.SH DESCRIPTION
+The
+.BR libabort
+library provides string functions that call
+.BR abort (3)
+rather than silently truncating or read or write out of bounds.
+.PP
+Each function is namespaced with the prefix
+.BR libabort_ ,
+and shorthand names are available without this prefix,
+however defining
+.BR LIBABORT_NO_SHORTHANDS
+before including
+.I <libabort.h>
+will cause these shorthands not to be defined.
+.PP
+The following functions are provided:
+.TP
+.BR libabort_stracpy (3)
+Copy a NUL terminated string into a fixed-size buffer.
+Returns the written string.
+.TP
+.BR libabort_stpacpy (3)
+Copy a NUL terminated string into a fixed-size buffer.
+Returns the end of the written string.
+.TP
+.BR libabort_stracat (3)
+Append a NUL terminated string to a NUL terminated string within a
+fixed-size buffer. Returns the augmented string.
+.TP
+.BR libabort_stpacat (3)
+Append a NUL terminated string to a NUL terminated string within a
+fixed-size buffer. Returns the end of the augmented string.
+.TP
+.BR libabort_stralen (3)
+Compute the length of string that is expected to be NUL terminated.
+.TP
+.BR libabort_saprintf (3)
+Format a string into a fixed-size buffer.
+.TP
+.BR libabort_vsaprintf (3)
+Format a string into a fixed-size buffer using a
+.I va_list
+argument list.
+
+.SH SEE ALSO
+None.
diff --git a/libabort.h b/libabort.h
new file mode 100644
index 0000000..333edb3
--- /dev/null
+++ b/libabort.h
@@ -0,0 +1,233 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef LIBABORT_H
+#define LIBABORT_H
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+#if defined(__GNUC__)
+# if defined(__clang__)
+# define LIBABORT_PRINTF__(FMT, ARGS) __attribute__((__format__(__printf__, FMT, ARGS)))
+# else
+# define LIBABORT_PRINTF__(FMT, ARGS) __attribute__((__format__(__gnu_printf__, FMT, ARGS)))
+# endif
+# define LIBABORT_NONNULL__(INDEX) __attribute__((__nonnull__(INDEX)))
+# define LIBABORT_ALL_NONNULL__ __attribute__((__nonnull__))
+# define LIBABORT_PURE__ __attribute__((__pure__))
+#else
+# define LIBABORT_PRINTF__(FMT, ARGS)
+# define LIBABORT_NONNULL__(INDEX)
+# define LIBABORT_ALL_NONNULL__
+# define LIBABORT_PURE__
+#endif
+
+
+#ifndef LIBABORT_NO_SHORTHANDS
+# ifndef stracpy
+# define stracpy libabort_stracpy
+# endif
+# ifndef saprintf
+# define saprintf libabort_saprintf
+# endif
+# ifndef vsaprintf
+# define vsaprintf libabort_vsaprintf
+# endif
+# ifndef stpacpy
+# define stpacpy libabort_stpacpy
+# endif
+# ifndef stracat
+# define stracat libabort_stracat
+# endif
+# ifndef stralen
+# define stralen libabort_stralen
+# endif
+# ifndef stpacat
+# define stpacat libabort_stpacat
+# endif
+#endif
+
+
+/**
+ * libabort_stracpy
+ *
+ * Copy a string like strncpy(3), but abort if truncation would occur
+ *
+ * @param dst The destination buffer
+ * @param src The source string
+ * @param size The size of `dst`, in bytes
+ * @return `dst`
+ */
+LIBABORT_ALL_NONNULL__
+inline char *
+libabort_stracpy(char *dst, const char *src, size_t size)
+{
+ size_t i;
+ for (i = 0; i < size; i++)
+ if ((dst[i] = src[i]) == '\0')
+ return dst;
+ abort();
+}
+
+
+/**
+ * libabort_stpacpy
+ *
+ * Copy a string like stpcpy(3), but abort if truncation would occur.
+ *
+ * @param dst The destination buffer
+ * @param src The source string
+ * @param size The size of `dst`, in bytes
+ * @return A pointer to the terminating NUL byte in `dst`
+ */
+LIBABORT_ALL_NONNULL__
+inline char *
+libabort_stpacpy(char *dst, const char *src, size_t size)
+{
+ size_t i;
+ for (i = 0; i < size; i++)
+ if ((dst[i] = src[i]) == '\0')
+ return &dst[i];
+ abort();
+}
+
+
+/**
+ * libabort_stracat
+ *
+ * Concatenate strings like strcat(3), but abort if truncation would occur.
+ *
+ * @param dst The destination buffer (must contain a NUL-terminated string)
+ * @param src The source string
+ * @param size The size of `dst`, in bytes
+ * @return `dst`
+ */
+LIBABORT_ALL_NONNULL__
+inline char *
+libabort_stracat(char *dst, const char *src, size_t size)
+{
+ size_t i, j;
+ for (i = 0; i < size; i++)
+ if (dst[i] == '\0')
+ break;
+ if (i == size)
+ abort();
+ for (j = 0; i < size; i++, j++)
+ if ((dst[i] = src[j]) == '\0')
+ return dst;
+ abort();
+}
+
+
+/**
+ * libabort_stralen
+ *
+ * Get the length of a string like strlen(3), but abort if no NUL byte is found
+ * within `size` bytes.
+ *
+ * @param str The string
+ * @param size The maximum number of bytes to examine
+ * @return The length of the string, excluding the terminating NUL byte
+ */
+LIBABORT_PURE__
+LIBABORT_ALL_NONNULL__
+inline size_t
+libabort_stralen(const char *str, size_t size)
+{
+ size_t i;
+ for (i = 0; i < size; i++)
+ if (!str[i])
+ return i;
+ abort();
+}
+
+
+/**
+ * libabort_stpacat
+ *
+ * Concatenate strings like stpcat(3) would (non-standard), but abort if
+ * truncation would occur.
+ *
+ * @param dst The destination buffer (must contain a NUL-terminated string)
+ * @param src The source string
+ * @param size The size of `dst`, in bytes
+ * @return A pointer to the terminating NUL byte in `dst`
+ */
+LIBABORT_ALL_NONNULL__
+inline char *
+libabort_stpacat(char *dst, const char *src, size_t size)
+{
+ size_t i, j;
+ for (i = 0; i < size; i++)
+ if (dst[i] == '\0')
+ break;
+ if (i == size)
+ abort();
+ for (j = 0; i < size; i++, j++)
+ if ((dst[i] = src[j]) == '\0')
+ return &dst[i];
+ abort();
+}
+
+
+/**
+ * libabort_vsaprintf
+ *
+ * Format a string like vsnprintf(3), but abort if truncation would occur
+ *
+ * @param buf The output buffer
+ * @param size The size of `buf`, in bytes
+ * @param fmt The format string
+ * @param ap The format arguments
+ * @return The number of bytes written excluding the terminating NUL byte
+ *
+ * NB! This function aborts if `size` is 0, use vsnprintf(3) get the size required for `buf`
+ */
+LIBABORT_NONNULL__(1)
+LIBABORT_NONNULL__(3)
+LIBABORT_PRINTF__(3, 0)
+inline int
+libabort_vsaprintf(char *buf, size_t size, const char *fmt, va_list ap)
+{
+ int n = vsnprintf(buf, size, fmt, ap);
+ if (n < 0 || (size_t)n >= size)
+ abort();
+ return n;
+}
+
+
+/**
+ * libabort_saprintf
+ *
+ * Format a string like snprintf(3), but abort if truncation would occur
+ *
+ * @param buf The output buffer
+ * @param size The size of `buf`, in bytes
+ * @param fmt The format string
+ * @return The number of bytes written excluding the terminating NUL byte
+ *
+ * NB! This function aborts if `size` is 0, use snprintf(3) get the size required for `buf`
+ */
+LIBABORT_NONNULL__(1)
+LIBABORT_NONNULL__(3)
+LIBABORT_PRINTF__(3, 4)
+inline int
+libabort_saprintf(char *buf, size_t size, const char *fmt, ...)
+{
+ va_list ap;
+ int r;
+ va_start(ap, fmt);
+ r = libabort_vsaprintf(buf, size, fmt, ap);
+ va_end(ap);
+ return r;
+}
+
+
+#undef LIBABORT_PRINTF__
+#undef LIBABORT_NONNULL__
+#undef LIBABORT_ALL_NONNULL__
+
+#endif
diff --git a/libabort_saprintf.3 b/libabort_saprintf.3
new file mode 100644
index 0000000..559d06f
--- /dev/null
+++ b/libabort_saprintf.3
@@ -0,0 +1,58 @@
+.TH LIBABORT_SAPRINTF 3 LIBABORT
+.SH NAME
+libabort_saprintf \- Format a string with bounds checking
+
+.SH SYNOPSIS
+.nf
+#include <libabort.h>
+
+int \fBlibabort_saprintf\fP(char *\fIbuf\fP, size_t \fIsize\fP, const char *\fIfmt\fP, ...);
+
+#if !defined(LIBABORT_NO_SHORTHANDS) && !defined(saprintf)
+# define \fBsaprintf\fP libabort_saprintf
+#endif
+.fi
+.PP
+Link with
+.IR -labort .
+
+.SH DESCRIPTION
+The
+.BR libabort_saprintf ()
+function formats a string according to
+.I fmt
+into the buffer
+.IR buf ,
+which is assumed to be
+.I size
+bytes long.
+.PP
+If the formatted output does not fit in the destination buffer
+(including the terminating NUL byte), or if
+.I size
+is zero, the
+.BR libabort_saprintf ()
+function calls
+.BR abort (3).
+
+.SH RETURN VALUE
+The
+.BR libabort_saprintf ()
+function returns the number of bytes written to
+.IR buf ,
+excluding the terminating NUL byte.
+
+.SH ERRORS
+The
+.BR libabort_saprintf ()
+function cannot fail.
+
+.SH HISTORY
+The
+.BR libabort_saprintf ()
+function added in version 1.0 of
+.BR libabort .
+
+.SH SEE ALSO
+.BR libabort (7),
+.BR libabort_vsaprintf (3)
diff --git a/libabort_saprintf.c b/libabort_saprintf.c
new file mode 100644
index 0000000..4468b91
--- /dev/null
+++ b/libabort_saprintf.c
@@ -0,0 +1,28 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+extern inline int libabort_saprintf(char *str, size_t size, const char *fmt, ...);
+
+#else
+
+int
+main(void)
+{
+ char buf[6];
+ int r;
+
+ INIT_TEST_ABORT();
+
+ EXPECT_NO_ABORT(r = libabort_saprintf(buf, sizeof(buf), "%s", "hello"));
+ EXPECT(r == 5);
+ EXPECT(!buf[r]);
+ EXPECT(!strcmp(buf, "hello"));
+
+ EXPECT_ABORT(libabort_saprintf(buf, sizeof(buf), "%s", "hello!"));
+ EXPECT_ABORT(libabort_saprintf(buf, 0, "%s", ""));
+
+ return 0;
+}
+
+#endif
diff --git a/libabort_stpacat.3 b/libabort_stpacat.3
new file mode 100644
index 0000000..2efdc23
--- /dev/null
+++ b/libabort_stpacat.3
@@ -0,0 +1,61 @@
+.TH LIBABORT_STPACAT 3 LIBABORT
+.SH NAME
+libabort_stpacat \- Append a string with bounds checking
+
+.SH SYNOPSIS
+.nf
+#include <libabort.h>
+
+char *\fBlibabort_stpacat\fP(char *\fIdst\fP, const char *\fIsrc\fP, size_t \fIsize\fP);
+
+#if !defined(LIBABORT_NO_SHORTHANDS) && !defined(stpacat)
+# define \fBstpacat\fP libabort_stpacat
+#endif
+.fi
+.PP
+Link with
+.IR -labort .
+
+.SH DESCRIPTION
+The
+.BR libabort_stpacat ()
+function appends the NUL terminated string
+.I src
+to the NUL terminated string
+.I dst
+which is assumed to refer to a buffer of
+.I size
+bytes.
+.PP
+The function verifies that the concatenated
+string fits in the destination buffer
+(including the terminating NUL). If the
+destination buffer is too small, or if
+.I dst
+is not NUL terminated within the first
+.I size
+bytes, the
+.BR libabort_stpacat ()
+function calls
+.BR abort (3).
+
+.SH RETURN VALUE
+The
+.BR libabort_stpacat ()
+function returns a pointer to the terminating NUL byte of the
+resulting string.
+
+.SH ERRORS
+The
+.BR libabort_stpacat ()
+function cannot fail.
+
+.SH HISTORY
+The
+.BR libabort_stpacat ()
+function added in version 1.0 of
+.BR libabort .
+
+.SH SEE ALSO
+.BR libabort (7),
+.BR libabort_stralen (3)
diff --git a/libabort_stpacat.c b/libabort_stpacat.c
new file mode 100644
index 0000000..35d8dd8
--- /dev/null
+++ b/libabort_stpacat.c
@@ -0,0 +1,30 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+extern inline char *libabort_stpacat(char *dst, const char *src, size_t size);
+
+#else
+
+int
+main(void)
+{
+ char buf[32];
+ char *p;
+
+ INIT_TEST_ABORT();
+
+ stracpy(buf, "hello", sizeof(buf));
+ EXPECT_NO_ABORT(p = stpacat(buf, " world", sizeof(buf)));
+ EXPECT(!strcmp(buf, "hello world"));
+ EXPECT(p == &buf[11]);
+ EXPECT(*p == '\0');
+
+ stracpy(buf, "hello", sizeof(buf));
+ EXPECT_ABORT(stpacat(buf, " world", 11));
+ EXPECT_ABORT(stpacat(buf, "", 0));
+
+ return 0;
+}
+
+#endif
diff --git a/libabort_stpacpy.3 b/libabort_stpacpy.3
new file mode 100644
index 0000000..3de9ea1
--- /dev/null
+++ b/libabort_stpacpy.3
@@ -0,0 +1,56 @@
+.TH LIBABORT_STPACPY 3 LIBABORT
+.SH NAME
+libabort_stpacpy \- Copy a string with bounds checking
+
+.SH SYNOPSIS
+.nf
+#include <libabort.h>
+
+char *\fBlibabort_stpacpy\fP(char *\fIdst\fP, const char *\fIsrc\fP, size_t \fIsize\fP);
+
+#if !defined(LIBABORT_NO_SHORTHANDS) && !defined(stpacpy)
+# define \fBstpacpy\fP libabort_stpacpy
+#endif
+.fi
+.PP
+Link with
+.IR -labort .
+
+.SH DESCRIPTION
+The
+.BR libabort_stpacpy ()
+function copies the NUL terminated string
+.I src
+into the buffer
+.IR dst ,
+which is assumed to be
+.I size
+bytes long.
+.PP
+If the string does not fit in the destination buffer
+(including the terminating NUL byte), the
+.BR libabort_stpacpy ()
+function calls
+.BR abort (3).
+
+.SH RETURN VALUE
+The
+.BR libabort_stpacpy ()
+function returns a pointer to the terminating NUL byte in
+.IR dst .
+
+.SH ERRORS
+The
+.BR libabort_stpacpy ()
+function cannot fail.
+
+.SH HISTORY
+The
+.BR libabort_stpacpy ()
+function added in version 1.0 of
+.BR libabort .
+
+.SH SEE ALSO
+.BR libabort (7),
+.BR libabort_stracpy (3),
+.BR libabort_stpacat (3)
diff --git a/libabort_stpacpy.c b/libabort_stpacpy.c
new file mode 100644
index 0000000..5c48d65
--- /dev/null
+++ b/libabort_stpacpy.c
@@ -0,0 +1,27 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+extern inline char *libabort_stpacpy(char *dst, const char *src, size_t size);
+
+#else
+
+int
+main(void)
+{
+ char buf[6];
+ char *p;
+
+ INIT_TEST_ABORT();
+
+ EXPECT_NO_ABORT(p = libabort_stpacpy(buf, "hello", sizeof(buf)));
+ EXPECT(p == &buf[5]);
+ EXPECT(!buf[5]);
+ EXPECT(!strcmp(buf, "hello"));
+
+ EXPECT_ABORT(libabort_stpacpy(buf, "abcdef", sizeof(buf)));
+
+ return 0;
+}
+
+#endif
diff --git a/libabort_stracat.3 b/libabort_stracat.3
new file mode 100644
index 0000000..16fe165
--- /dev/null
+++ b/libabort_stracat.3
@@ -0,0 +1,62 @@
+.TH LIBABORT_STRACAT 3 LIBABORT
+.SH NAME
+libabort_stracat \- Append a string with bounds checking
+
+.SH SYNOPSIS
+.nf
+#include <libabort.h>
+
+char *\fBlibabort_stracat\fP(char *\fIdst\fP, const char *\fIsrc\fP, size_t \fIsize\fP);
+
+#if !defined(LIBABORT_NO_SHORTHANDS) && !defined(stracat)
+# define \fBstracat\fP libabort_stracat
+#endif
+.fi
+.PP
+Link with
+.IR -labort .
+
+.SH DESCRIPTION
+The
+.BR libabort_stracat ()
+function appends the NUL terminated string
+.I src
+to the NUL terminated string
+.I dst
+in the buffer
+.IR dst ,
+which is assumed to be
+.I size
+bytes long.
+.PP
+If the concatenated string does not fit in the destination buffer
+(including the terminating NUL byte), or if
+.I dst
+is not NUL terminated within the first
+.I size
+bytes, the
+.BR libabort_stracat ()
+function calls
+.BR abort (3).
+
+.SH RETURN VALUE
+The
+.BR libabort_stracat ()
+function returns
+.IR dst .
+
+.SH ERRORS
+The
+.BR libabort_stracat ()
+function cannot fail.
+
+.SH HISTORY
+The
+.BR libabort_stracat ()
+function added in version 1.0 of
+.BR libabort .
+
+.SH SEE ALSO
+.BR libabort (7),
+.BR libabort_stpacat (3),
+.BR libabort_stracpy (3)
diff --git a/libabort_stracat.c b/libabort_stracat.c
new file mode 100644
index 0000000..a3a32fb
--- /dev/null
+++ b/libabort_stracat.c
@@ -0,0 +1,33 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+extern inline char *libabort_stracat(char *dst, const char *src, size_t size);
+
+#else
+
+int
+main(void)
+{
+ char buf[16];
+ char *p;
+
+ INIT_TEST_ABORT();
+
+ buf[0] = '\0';
+ EXPECT_NO_ABORT(p = libabort_stracat(buf, "hello", sizeof(buf)));
+ EXPECT(p == buf);
+ EXPECT(!buf[5]);
+ EXPECT(!strcmp(buf, "hello"));
+
+ EXPECT_NO_ABORT(p = libabort_stracat(buf, " world", sizeof(buf)));
+ EXPECT(p == buf);
+ EXPECT(!buf[11]);
+ EXPECT(!strcmp(buf, "hello world"));
+
+ EXPECT_ABORT(libabort_stracat(buf, " 0123456789abcdef", sizeof(buf)));
+
+ return 0;
+}
+
+#endif
diff --git a/libabort_stracpy.3 b/libabort_stracpy.3
new file mode 100644
index 0000000..414fab1
--- /dev/null
+++ b/libabort_stracpy.3
@@ -0,0 +1,56 @@
+.TH LIBABORT_STRACPY 3 LIBABORT
+.SH NAME
+libabort_stracpy \- Copy a string with bounds checking
+
+.SH SYNOPSIS
+.nf
+#include <libabort.h>
+
+char *\fBlibabort_stracpy\fP(char *\fIdst\fP, const char *\fIsrc\fP, size_t \fIsize\fP);
+
+#if !defined(LIBABORT_NO_SHORTHANDS) && !defined(stracpy)
+# define \fBstracpy\fP libabort_stracpy
+#endif
+.fi
+.PP
+Link with
+.IR -labort .
+
+.SH DESCRIPTION
+The
+.BR libabort_stracpy ()
+function copies the NUL terminated string
+.I src
+into the buffer
+.IR dst ,
+which is assumed to be
+.I size
+bytes long.
+.PP
+If the string does not fit in the destination buffer
+(including the terminating NUL byte), the
+.BR libabort_stracpy ()
+function calls
+.BR abort (3).
+
+.SH RETURN VALUE
+The
+.BR libabort_stracpy ()
+function returns
+.IR dst .
+
+.SH ERRORS
+The
+.BR libabort_stracpy ()
+function cannot fail.
+
+.SH HISTORY
+The
+.BR libabort_stracpy ()
+function added in version 1.0 of
+.BR libabort .
+
+.SH SEE ALSO
+.BR libabort (7),
+.BR libabort_stpacpy (3),
+.BR libabort_stracat (3)
diff --git a/libabort_stracpy.c b/libabort_stracpy.c
new file mode 100644
index 0000000..53541a8
--- /dev/null
+++ b/libabort_stracpy.c
@@ -0,0 +1,28 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+extern inline char *libabort_stracpy(char *dst, const char *src, size_t size);
+
+#else
+
+int
+main(void)
+{
+ char buf[6];
+ char *p;
+
+ INIT_TEST_ABORT();
+
+ EXPECT_NO_ABORT(p = libabort_stracpy(buf, "hello", sizeof(buf)));
+ EXPECT(p == buf);
+ EXPECT(!buf[5]);
+ EXPECT(!strcmp(buf, "hello"));
+
+ EXPECT_ABORT(libabort_stracpy(buf, "hello!", sizeof(buf)));
+ EXPECT_ABORT(libabort_stracpy(buf, "", 0));
+
+ return 0;
+}
+
+#endif
diff --git a/libabort_stralen.3 b/libabort_stralen.3
new file mode 100644
index 0000000..d2ef0d9
--- /dev/null
+++ b/libabort_stralen.3
@@ -0,0 +1,53 @@
+.TH LIBABORT_STRALEN 3 LIBABORT
+.SH NAME
+libabort_stralen \- Get string length within a bounded size
+
+.SH SYNOPSIS
+.nf
+#include <libabort.h>
+
+size_t \fBlibabort_stralen\fP(const char *\fIstr\fP, size_t \fIsize\fP);
+
+#if !defined(LIBABORT_NO_SHORTHANDS) && !defined(stralen)
+# define \fBstralen\fP libabort_stralen
+#endif
+.fi
+
+.SH DESCRIPTION
+The
+.BR libabort_stralen ()
+function returns the length of the NUL terminated string
+.I str
+but will not read more than
+.I size
+bytes.
+.PP
+If no NUL terminator is found within the first
+.I size
+bytes, or if
+.I size
+is zero, the
+.BR libabort_stralen ()
+function calls
+.BR abort (3).
+
+.SH RETURN VALUE
+The
+.BR libabort_stralen ()
+function returns the length of
+.IR str .
+
+.SH ERRORS
+The
+.BR libabort_stralen ()
+function cannot fail.
+
+.SH HISTORY
+The
+.BR libabort_stralen ()
+function added in version 1.0 of
+.BR libabort .
+
+.SH SEE ALSO
+.BR libabort (7),
+.BR libabort_stpacat (3)
diff --git a/libabort_stralen.c b/libabort_stralen.c
new file mode 100644
index 0000000..8985256
--- /dev/null
+++ b/libabort_stralen.c
@@ -0,0 +1,34 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+extern inline size_t libabort_stralen(const char *str, size_t size);
+
+#else
+
+volatile size_t test_size_t_discard;
+
+int
+main(void)
+{
+ size_t r;
+
+ INIT_TEST_ABORT();
+
+ EXPECT_NO_ABORT(r = libabort_stralen("", 1));
+ EXPECT(r == 0);
+
+ EXPECT_NO_ABORT(r = libabort_stralen("hello", 6));
+ EXPECT(r == 5);
+
+ EXPECT_NO_ABORT(r = libabort_stralen("hello", 10));
+ EXPECT(r == 5);
+
+ EXPECT_ABORT(test_size_t_discard = libabort_stralen("hello", 4));
+ EXPECT_ABORT(test_size_t_discard = libabort_stralen("hello", 5));
+ EXPECT_ABORT(test_size_t_discard = libabort_stralen("", 0));
+
+ return 0;
+}
+
+#endif
diff --git a/libabort_vsaprintf.3 b/libabort_vsaprintf.3
new file mode 100644
index 0000000..8fbd385
--- /dev/null
+++ b/libabort_vsaprintf.3
@@ -0,0 +1,58 @@
+.TH LIBABORT_VSAPRINTF 3 LIBABORT
+.SH NAME
+libabort_vsaprintf \- Format a string with bounds checking
+
+.SH SYNOPSIS
+.nf
+#include <libabort.h>
+
+int \fBlibabort_vsaprintf\fP(char *\fIbuf\fP, size_t \fIsize\fP, const char *\fIfmt\fP, va_list \fIap\fP);
+
+#if !defined(LIBABORT_NO_SHORTHANDS) && !defined(vsaprintf)
+# define \fBvsaprintf\fP libabort_vsaprintf
+#endif
+.fi
+.PP
+Link with
+.IR -labort .
+
+.SH DESCRIPTION
+The
+.BR libabort_vsaprintf ()
+function formats a string according to
+.I fmt
+into the buffer
+.IR buf ,
+which is assumed to be
+.I size
+bytes long.
+.PP
+If the formatted output does not fit in the destination buffer
+(including the terminating NUL byte), or if
+.I size
+is zero, the
+.BR libabort_vsaprintf ()
+function calls
+.BR abort (3).
+
+.SH RETURN VALUE
+The
+.BR libabort_vsaprintf ()
+function returns the number of bytes written to
+.IR buf ,
+excluding the terminating NUL byte.
+
+.SH ERRORS
+The
+.BR libabort_vsaprintf ()
+function cannot fail.
+
+.SH HISTORY
+The
+.BR libabort_vsaprintf ()
+function added in version 1.0 of
+.BR libabort .
+
+.SH SEE ALSO
+.BR libabort (7),
+.BR libabort_saprintf (3)
diff --git a/libabort_vsaprintf.c b/libabort_vsaprintf.c
new file mode 100644
index 0000000..844e68f
--- /dev/null
+++ b/libabort_vsaprintf.c
@@ -0,0 +1,11 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+extern inline int libabort_vsaprintf(char *str, size_t size, const char *fmt, va_list ap);
+
+#else
+
+TESTED /* via libabort_saprintf */
+
+#endif
diff --git a/mk/linux.mk b/mk/linux.mk
new file mode 100644
index 0000000..ad58f69
--- /dev/null
+++ b/mk/linux.mk
@@ -0,0 +1,6 @@
+LIBEXT = so
+LIBFLAGS = -shared -Wl,-soname,lib$(LIB_NAME).$(LIBEXT).$(LIB_MAJOR)
+LIBMAJOREXT = $(LIBEXT).$(LIB_MAJOR)
+LIBMINOREXT = $(LIBEXT).$(LIB_VERSION)
+
+FIX_INSTALL_NAME = :
diff --git a/mk/macos.mk b/mk/macos.mk
new file mode 100644
index 0000000..448f114
--- /dev/null
+++ b/mk/macos.mk
@@ -0,0 +1,6 @@
+LIBEXT = dylib
+LIBFLAGS = -dynamiclib -Wl,-compatibility_version,$(LIB_MAJOR) -Wl,-current_version,$(LIB_VERSION)
+LIBMAJOREXT = $(LIB_MAJOR).$(LIBEXT)
+LIBMINOREXT = $(LIB_VERSION).$(LIBEXT)
+
+FIX_INSTALL_NAME = install_name_tool -id "$(PREFIX)/lib/libabort.$(LIBMAJOREXT)"
diff --git a/mk/windows.mk b/mk/windows.mk
new file mode 100644
index 0000000..ed5ec8d
--- /dev/null
+++ b/mk/windows.mk
@@ -0,0 +1,6 @@
+LIBEXT = dll
+LIBFLAGS = -shared
+LIBMAJOREXT = $(LIB_MAJOR).$(LIBEXT)
+LIBMINOREXT = $(LIB_VERSION).$(LIBEXT)
+
+FIX_INSTALL_NAME = :
diff --git a/saprintf.3libabort b/saprintf.3libabort
new file mode 120000
index 0000000..92177ad
--- /dev/null
+++ b/saprintf.3libabort
@@ -0,0 +1 @@
+libabort_saprintf.3 \ No newline at end of file
diff --git a/stpacat.3libabort b/stpacat.3libabort
new file mode 120000
index 0000000..2da7a39
--- /dev/null
+++ b/stpacat.3libabort
@@ -0,0 +1 @@
+libabort_stpacat.3 \ No newline at end of file
diff --git a/stpacpy.3libabort b/stpacpy.3libabort
new file mode 120000
index 0000000..89d68dd
--- /dev/null
+++ b/stpacpy.3libabort
@@ -0,0 +1 @@
+libabort_stpacpy.3 \ No newline at end of file
diff --git a/stracat.3libabort b/stracat.3libabort
new file mode 120000
index 0000000..2569712
--- /dev/null
+++ b/stracat.3libabort
@@ -0,0 +1 @@
+libabort_stracat.3 \ No newline at end of file
diff --git a/stracpy.3libabort b/stracpy.3libabort
new file mode 120000
index 0000000..a663685
--- /dev/null
+++ b/stracpy.3libabort
@@ -0,0 +1 @@
+libabort_stracpy.3 \ No newline at end of file
diff --git a/stralen.3libabort b/stralen.3libabort
new file mode 120000
index 0000000..b5987e6
--- /dev/null
+++ b/stralen.3libabort
@@ -0,0 +1 @@
+libabort_stralen.3 \ No newline at end of file
diff --git a/vsaprintf.3libabort b/vsaprintf.3libabort
new file mode 120000
index 0000000..51d57ba
--- /dev/null
+++ b/vsaprintf.3libabort
@@ -0,0 +1 @@
+libabort_vsaprintf.3 \ No newline at end of file