shithub: rgbds

Download patch

ref: bdcef6f252d6a39010f46c490a955d9aa951102c
parent: 54293a91844bc812091f10f8db673c71fea6b23d
author: Rangi <[email protected]>
date: Sun Nov 21 11:16:54 EST 2021

Refactor error reporting to simplify BSD-style `err` (#949)


--- a/Makefile
+++ b/Makefile
@@ -72,9 +72,9 @@
 	src/asm/symbol.o \
 	src/asm/util.o \
 	src/asm/warning.o \
-	src/extern/err.o \
 	src/extern/getopt.o \
 	src/extern/utf8decoder.o \
+	src/error.o \
 	src/hashmap.o \
 	src/linkdefs.o \
 	src/opmath.o
@@ -90,8 +90,8 @@
 	src/link/script.o \
 	src/link/section.o \
 	src/link/symbol.o \
-	src/extern/err.o \
 	src/extern/getopt.o \
+	src/error.o \
 	src/hashmap.o \
 	src/linkdefs.o \
 	src/opmath.o
@@ -98,15 +98,15 @@
 
 rgbfix_obj := \
 	src/fix/main.o \
-	src/extern/err.o \
-	src/extern/getopt.o
+	src/extern/getopt.o \
+	src/error.o
 
 rgbgfx_obj := \
 	src/gfx/gb.o \
 	src/gfx/main.o \
 	src/gfx/makepng.o \
-	src/extern/err.o \
-	src/extern/getopt.o
+	src/extern/getopt.o \
+	src/error.o
 
 rgbasm: ${rgbasm_obj}
 	$Q${CC} ${REALLDFLAGS} -o $@ ${rgbasm_obj} ${REALCFLAGS} src/version.c -lm
--- /dev/null
+++ b/include/error.h
@@ -1,0 +1,21 @@
+/*
+ * This file is part of RGBDS.
+ *
+ * Copyright (c) 1997-2021, RGBDS contributors.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef RGBDS_ERROR_H
+#define RGBDS_ERROR_H
+
+#include "helpers.h"
+#include "platform.h"
+
+void warn(char const NONNULL(fmt), ...) format_(printf, 1, 2);
+void warnx(char const NONNULL(fmt), ...) format_(printf, 1, 2);
+
+_Noreturn void err(char const NONNULL(fmt), ...) format_(printf, 1, 2);
+_Noreturn void errx(char const NONNULL(fmt), ...) format_(printf, 1, 2);
+
+#endif /* RGBDS_ERROR_H */
--- a/include/extern/err.h
+++ /dev/null
@@ -1,44 +1,0 @@
-/*
- * This file is part of RGBDS.
- *
- * Copyright (c) 1997-2018, RGBDS contributors.
- *
- * SPDX-License-Identifier: MIT
- */
-
-#ifndef EXTERN_ERR_H
-#define EXTERN_ERR_H
-
-#ifdef ERR_IN_LIBC
-
-#include <err.h>
-
-#else /* ERR_IN_LIBC */
-
-#include <stdarg.h>
-
-#include "helpers.h"
-
-#define warn rgbds_warn
-#define vwarn rgbds_vwarn
-#define warnx rgbds_warnx
-#define vwarnx rgbds_vwarnx
-
-#define err rgbds_err
-#define verr rgbds_verr
-#define errx rgbds_errx
-#define verrx rgbds_verrx
-
-void warn(char const *fmt, ...) format_(printf, 1, 2);
-void vwarn(char const *fmt, va_list ap) format_(printf, 1, 0);
-void warnx(char const *fmt, ...) format_(printf, 1, 2);
-void vwarnx(char const *fmt, va_list ap) format_(printf, 1, 0);
-
-_Noreturn void err(int status, char const *fmt, ...) format_(printf, 2, 3);
-_Noreturn void verr(int status, char const *fmt, va_list ap) format_(printf, 2, 0);
-_Noreturn void errx(int status, char const *fmt, ...) format_(printf, 2, 3);
-_Noreturn void verrx(int status, char const *fmt, va_list ap) format_(printf, 2, 0);
-
-#endif /* ERR_IN_LIBC */
-
-#endif /* EXTERN_ERR_H */
--- a/include/gfx/main.h
+++ b/include/gfx/main.h
@@ -13,7 +13,7 @@
 #include <stdbool.h>
 #include <stdint.h>
 
-#include "extern/err.h"
+#include "error.h"
 
 struct Options {
 	bool debug;
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -7,7 +7,7 @@
 #
 
 set(common_src
-    "extern/err.c"
+    "error.c"
     "extern/getopt.c"
     "version.c"
     )
--- a/src/asm/main.c
+++ b/src/asm/main.c
@@ -30,10 +30,10 @@
 #include "asm/warning.h"
 #include "parser.h"
 
-#include "extern/err.h"
 #include "extern/getopt.h"
 
 #include "helpers.h"
+#include "error.h"
 #include "version.h"
 
 #ifdef __clang__
@@ -74,7 +74,7 @@
 	char *dest = escaped_str;
 
 	if (escaped_str == NULL)
-		err(1, "%s: Failed to allocate memory", __func__);
+		err("%s: Failed to allocate memory", __func__);
 
 	while (*str) {
 		/* All dollars needs to be doubled */
@@ -192,7 +192,7 @@
 			if (strlen(musl_optarg) == 2)
 				opt_B(&musl_optarg[1]);
 			else
-				errx(1, "Must specify exactly 2 characters for option 'b'");
+				errx("Must specify exactly 2 characters for option 'b'");
 			break;
 
 			char *equals;
@@ -214,7 +214,7 @@
 			if (strlen(musl_optarg) == 4)
 				opt_G(&musl_optarg[1]);
 			else
-				errx(1, "Must specify exactly 4 characters for option 'g'");
+				errx("Must specify exactly 4 characters for option 'g'");
 			break;
 
 		case 'h':
@@ -235,7 +235,7 @@
 			else
 				dependfile = fopen(musl_optarg, "w");
 			if (dependfile == NULL)
-				err(1, "Could not open dependfile %s", musl_optarg);
+				err("Could not open dependfile %s", musl_optarg);
 			break;
 
 		case 'o':
@@ -247,10 +247,10 @@
 			fill = strtoul(musl_optarg, &ep, 0);
 
 			if (musl_optarg[0] == '\0' || *ep != '\0')
-				errx(1, "Invalid argument for option 'p'");
+				errx("Invalid argument for option 'p'");
 
 			if (fill < 0 || fill > 0xFF)
-				errx(1, "Argument for option 'p' must be between 0 and 0xFF");
+				errx("Argument for option 'p' must be between 0 and 0xFF");
 
 			opt_P(fill);
 			break;
@@ -259,7 +259,7 @@
 			maxDepth = strtoul(musl_optarg, &ep, 0);
 
 			if (musl_optarg[0] == '\0' || *ep != '\0')
-				errx(1, "Invalid argument for option 'r'");
+				errx("Invalid argument for option 'r'");
 			break;
 
 		case 'V':
@@ -299,7 +299,7 @@
 				targetFileName = realloc(targetFileName,
 							 targetFileNameLen + newTargetLen + 1);
 				if (targetFileName == NULL)
-					err(1, "Cannot append new file to target file list");
+					err("Cannot append new file to target file list");
 				memcpy(&targetFileName[targetFileNameLen], newTarget, newTargetLen);
 				if (depType == 'Q')
 					free(newTarget);
@@ -336,7 +336,7 @@
 
 	if (dependfile) {
 		if (!targetFileName)
-			errx(1, "Dependency files can only be created if a target file is specified with either -o, -MQ or -MT");
+			errx("Dependency files can only be created if a target file is specified with either -o, -MQ or -MT");
 
 		fprintf(dependfile, "%s: %s\n", targetFileName, mainFileName);
 	}
@@ -357,7 +357,7 @@
 	sect_CheckUnionClosed();
 
 	if (nbErrors != 0)
-		errx(1, "Assembly aborted (%u error%s)!", nbErrors,
+		errx("Assembly aborted (%u error%s)!", nbErrors,
 			nbErrors == 1 ? "" : "s");
 
 	// If parse aborted due to missing an include, and `-MG` was given, exit normally
--- a/src/asm/output.c
+++ b/src/asm/output.c
@@ -27,8 +27,7 @@
 #include "asm/symbol.h"
 #include "asm/warning.h"
 
-#include "extern/err.h"
-
+#include "error.h"
 #include "linkdefs.h"
 #include "platform.h" // strdup
 
@@ -528,7 +527,7 @@
 		f = fdopen(1, "wb");
 
 	if (!f)
-		err(1, "Couldn't write file '%s'", objectName);
+		err("Couldn't write file '%s'", objectName);
 
 	/* Also write symbols that weren't written above */
 	sym_ForEach(registerUnregisteredSymbol, NULL);
--- a/src/asm/section.c
+++ b/src/asm/section.c
@@ -15,7 +15,7 @@
 #include "asm/symbol.h"
 #include "asm/warning.h"
 
-#include "extern/err.h"
+#include "error.h"
 #include "platform.h" // strdup
 
 uint8_t fillByte;
--- a/src/asm/symbol.c
+++ b/src/asm/symbol.c
@@ -29,8 +29,7 @@
 #include "asm/util.h"
 #include "asm/warning.h"
 
-#include "extern/err.h"
-
+#include "error.h"
 #include "hashmap.h"
 #include "helpers.h"
 #include "version.h"
--- a/src/asm/warning.c
+++ b/src/asm/warning.c
@@ -18,7 +18,7 @@
 #include "asm/main.h"
 #include "asm/warning.h"
 
-#include "extern/err.h"
+#include "error.h"
 
 unsigned int nbErrors = 0;
 
@@ -215,7 +215,7 @@
 		if (!strcmp(flag, warningFlags[id])) {
 			/* We got a match! */
 			if (setError)
-				errx(1, "Cannot make meta warning \"%s\" into an error",
+				errx("Cannot make meta warning \"%s\" into an error",
 				     flag);
 
 			for (uint8_t const *ptr = metaWarningCommands[id - META_WARNINGS_START];
--- /dev/null
+++ b/src/error.c
@@ -1,0 +1,87 @@
+/*
+ * This file is part of RGBDS.
+ *
+ * Copyright (c) 2005-2021, Rich Felker and RGBDS contributors.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "error.h"
+#include "platform.h"
+
+static void vwarn(char const NONNULL(fmt), va_list ap)
+{
+	fprintf(stderr, "warning: ");
+	vfprintf(stderr, fmt, ap);
+	fputs(": ", stderr);
+	perror(NULL);
+}
+
+static void vwarnx(char const NONNULL(fmt), va_list ap)
+{
+	fprintf(stderr, "warning");
+	fputs(": ", stderr);
+	vfprintf(stderr, fmt, ap);
+	putc('\n', stderr);
+}
+
+_Noreturn static void verr(char const NONNULL(fmt), va_list ap)
+{
+	fprintf(stderr, "error: ");
+	vfprintf(stderr, fmt, ap);
+	fputs(": ", stderr);
+	fputs(strerror(errno), stderr);
+	putc('\n', stderr);
+	exit(1);
+}
+
+_Noreturn static void verrx(char const NONNULL(fmt), va_list ap)
+{
+	fprintf(stderr, "error");
+	fputs(": ", stderr);
+	vfprintf(stderr, fmt, ap);
+	putc('\n', stderr);
+	exit(1);
+}
+
+void warn(char const NONNULL(fmt), ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	vwarn(fmt, ap);
+	va_end(ap);
+}
+
+void warnx(char const NONNULL(fmt), ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	vwarnx(fmt, ap);
+	va_end(ap);
+}
+
+_Noreturn void err(char const NONNULL(fmt), ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	verr(fmt, ap);
+	va_end(ap);
+}
+
+_Noreturn void errx(char const NONNULL(fmt), ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	verrx(fmt, ap);
+	va_end(ap);
+}
--- a/src/extern/err.c
+++ /dev/null
@@ -1,94 +1,0 @@
-/*
- * This file is part of RGBDS.
- *
- * Copyright (c) 2005-2018, Rich Felker and RGBDS contributors.
- *
- * SPDX-License-Identifier: MIT
- */
-
-#include <errno.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "extern/err.h"
-
-void rgbds_vwarn(char const *fmt, va_list ap)
-{
-	fprintf(stderr, "warning: ");
-	if (fmt) {
-		vfprintf(stderr, fmt, ap);
-		fputs(": ", stderr);
-	}
-	perror(NULL);
-}
-
-void rgbds_vwarnx(char const *fmt, va_list ap)
-{
-	fprintf(stderr, "warning");
-	if (fmt) {
-		fputs(": ", stderr);
-		vfprintf(stderr, fmt, ap);
-	}
-	putc('\n', stderr);
-}
-
-_Noreturn void rgbds_verr(int status, char const *fmt, va_list ap)
-{
-	fprintf(stderr, "error: ");
-	if (fmt) {
-		vfprintf(stderr, fmt, ap);
-		fputs(": ", stderr);
-	}
-	fputs(strerror(errno), stderr);
-	putc('\n', stderr);
-	exit(status);
-}
-
-_Noreturn void rgbds_verrx(int status, char const *fmt, va_list ap)
-{
-	fprintf(stderr, "error");
-	if (fmt) {
-		fputs(": ", stderr);
-		vfprintf(stderr, fmt, ap);
-	}
-	putc('\n', stderr);
-	exit(status);
-}
-
-void rgbds_warn(char const *fmt, ...)
-{
-	va_list ap;
-
-	va_start(ap, fmt);
-	vwarn(fmt, ap);
-	va_end(ap);
-}
-
-void rgbds_warnx(char const *fmt, ...)
-{
-	va_list ap;
-
-	va_start(ap, fmt);
-	vwarnx(fmt, ap);
-	va_end(ap);
-}
-
-_Noreturn void rgbds_err(int status, char const *fmt, ...)
-{
-	va_list ap;
-
-	va_start(ap, fmt);
-	verr(status, fmt, ap);
-	va_end(ap);
-}
-
-_Noreturn void rgbds_errx(int status, char const *fmt, ...)
-{
-	va_list ap;
-
-	va_start(ap, fmt);
-	verrx(status, fmt, ap);
-	va_end(ap);
-}
--- a/src/gfx/gb.c
+++ b/src/gfx/gb.c
@@ -20,7 +20,7 @@
 
 	newdata = calloc(gb->size, 1);
 	if (!newdata)
-		err(1, "%s: Failed to allocate memory for new data", __func__);
+		err("%s: Failed to allocate memory for new data", __func__);
 
 	for (i = 0; i < gb->size; i++) {
 		newbyte = i / (8 * depth) * width * 8 * depth;
@@ -65,7 +65,7 @@
 
 	f = fopen(opts->outfile, "wb");
 	if (!f)
-		err(1, "%s: Opening output file '%s' failed", __func__,
+		err("%s: Opening output file '%s' failed", __func__,
 		    opts->outfile);
 
 	fwrite(gb->data, 1, gb->size - gb->trim * 8 * depth, f);
@@ -141,7 +141,7 @@
 
 	tile_yflip = malloc(tile_size);
 	if (!tile_yflip)
-		err(1, "%s: Failed to allocate memory for Y flip of tile",
+		err("%s: Failed to allocate memory for Y flip of tile",
 		    __func__);
 	yflip(tile, tile_yflip, tile_size);
 	index = get_tile_index(tile_yflip, tiles, num_tiles, tile_size);
@@ -153,7 +153,7 @@
 
 	tile_xflip = malloc(tile_size);
 	if (!tile_xflip)
-		err(1, "%s: Failed to allocate memory for X flip of tile",
+		err("%s: Failed to allocate memory for X flip of tile",
 		    __func__);
 	xflip(tile, tile_xflip, tile_size);
 	index = get_tile_index(tile_xflip, tiles, num_tiles, tile_size);
@@ -198,13 +198,13 @@
 
 	tiles = calloc(max_tiles, sizeof(*tiles));
 	if (!tiles)
-		err(1, "%s: Failed to allocate memory for tiles", __func__);
+		err("%s: Failed to allocate memory for tiles", __func__);
 	num_tiles = 0;
 
 	if (*opts->tilemapfile) {
 		tilemap->data = calloc(max_tiles, sizeof(*tilemap->data));
 		if (!tilemap->data)
-			err(1, "%s: Failed to allocate memory for tilemap data",
+			err("%s: Failed to allocate memory for tilemap data",
 			    __func__);
 		tilemap->size = 0;
 	}
@@ -212,7 +212,7 @@
 	if (*opts->attrmapfile) {
 		attrmap->data = calloc(max_tiles, sizeof(*attrmap->data));
 		if (!attrmap->data)
-			err(1, "%s: Failed to allocate memory for attrmap data",
+			err("%s: Failed to allocate memory for attrmap data",
 			    __func__);
 		attrmap->size = 0;
 	}
@@ -222,7 +222,7 @@
 		flags = 0;
 		tile = malloc(tile_size);
 		if (!tile)
-			err(1, "%s: Failed to allocate memory for tile",
+			err("%s: Failed to allocate memory for tile",
 			    __func__);
 		/*
 		 * If the input image doesn't fill the last tile,
@@ -269,7 +269,7 @@
 		free(gb->data);
 		gb->data = malloc(tile_size * num_tiles);
 		if (!gb->data)
-			err(1, "%s: Failed to allocate memory for tile data",
+			err("%s: Failed to allocate memory for tile data",
 			    __func__);
 		for (i = 0; i < num_tiles; i++) {
 			tile = tiles[i];
@@ -292,7 +292,7 @@
 
 	f = fopen(opts->tilemapfile, "wb");
 	if (!f)
-		err(1, "%s: Opening tilemap file '%s' failed", __func__,
+		err("%s: Opening tilemap file '%s' failed", __func__,
 		    opts->tilemapfile);
 
 	fwrite(tilemap->data, 1, tilemap->size, f);
@@ -309,7 +309,7 @@
 
 	f = fopen(opts->attrmapfile, "wb");
 	if (!f)
-		err(1, "%s: Opening attrmap file '%s' failed", __func__,
+		err("%s: Opening attrmap file '%s' failed", __func__,
 		    opts->attrmapfile);
 
 	fwrite(attrmap->data, 1, attrmap->size, f);
@@ -352,7 +352,7 @@
 
 	f = fopen(opts->palfile, "wb");
 	if (!f)
-		err(1, "%s: Opening palette file '%s' failed", __func__,
+		err("%s: Opening palette file '%s' failed", __func__,
 		    opts->palfile);
 
 	for (i = 0; i < raw_image->num_colors; i++) {
--- a/src/gfx/main.c
+++ b/src/gfx/main.c
@@ -167,7 +167,7 @@
 	opts.infile = argv[argc - 1];
 
 	if (depth != 1 && depth != 2)
-		errx(1, "Depth option must be either 1 or 2.");
+		errx("Depth option must be either 1 or 2.");
 
 	colors = 1 << depth;
 
@@ -200,17 +200,17 @@
 		opts.trim = png_options.trim;
 
 	if (raw_image->width % 8) {
-		errx(1, "Input PNG file %s not sized correctly. The image's width must be a multiple of 8.",
+		errx("Input PNG file %s not sized correctly. The image's width must be a multiple of 8.",
 		     opts.infile);
 	}
 	if (raw_image->width / 8 > 1 && raw_image->height % 8) {
-		errx(1, "Input PNG file %s not sized correctly. If the image is more than 1 tile wide, its height must be a multiple of 8.",
+		errx("Input PNG file %s not sized correctly. If the image is more than 1 tile wide, its height must be a multiple of 8.",
 		     opts.infile);
 	}
 
 	if (opts.trim &&
 	    opts.trim > (raw_image->width / 8) * (raw_image->height / 8) - 1) {
-		errx(1, "Trim (%d) for input raw_image file '%s' too large (max: %u)",
+		errx("Trim (%d) for input raw_image file '%s' too large (max: %u)",
 		     opts.trim, opts.infile,
 		     (raw_image->width / 8) * (raw_image->height / 8) - 1);
 	}
--- a/src/gfx/makepng.c
+++ b/src/gfx/makepng.c
@@ -32,7 +32,7 @@
 
 	f = fopen(opts->infile, "rb");
 	if (!f)
-		err(1, "Opening input png file '%s' failed", opts->infile);
+		err("Opening input png file '%s' failed", opts->infile);
 
 	initialize_png(&img, f);
 
@@ -54,7 +54,7 @@
 		raw_image = truecolor_png_to_raw(&img); break;
 	default:
 		/* Shouldn't happen, but might as well handle just in case. */
-		errx(1, "Input PNG file is of invalid color type.");
+		errx("Input PNG file is of invalid color type.");
 	}
 
 	get_text(&img, png_options);
@@ -83,7 +83,7 @@
 	if (opts->debug) {
 		outfile = malloc(strlen(opts->infile) + 5);
 		if (!outfile)
-			err(1, "%s: Failed to allocate memory for outfile",
+			err("%s: Failed to allocate memory for outfile",
 			    __func__);
 		strcpy(outfile, opts->infile);
 		strcat(outfile, ".out");
@@ -93,16 +93,16 @@
 
 	f = fopen(outfile, "wb");
 	if (!f)
-		err(1, "Opening output png file '%s' failed", outfile);
+		err("Opening output png file '%s' failed", outfile);
 
 	img.png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
 					  NULL, NULL, NULL);
 	if (!img.png)
-		errx(1, "Creating png structure failed");
+		errx("Creating png structure failed");
 
 	img.info = png_create_info_struct(img.png);
 	if (!img.info)
-		errx(1, "Creating png info structure failed");
+		errx("Creating png info structure failed");
 
 	if (setjmp(png_jmpbuf(img.png)))
 		exit(1);
@@ -115,7 +115,7 @@
 
 	png_palette = malloc(sizeof(*png_palette) * raw_image->num_colors);
 	if (!png_palette)
-		err(1, "%s: Failed to allocate memory for PNG palette",
+		err("%s: Failed to allocate memory for PNG palette",
 		    __func__);
 	for (i = 0; i < raw_image->num_colors; i++) {
 		png_palette[i].red   = raw_image->palette[i].red;
@@ -159,11 +159,11 @@
 	img->png = png_create_read_struct(PNG_LIBPNG_VER_STRING,
 					  NULL, NULL, NULL);
 	if (!img->png)
-		errx(1, "Creating png structure failed");
+		errx("Creating png structure failed");
 
 	img->info = png_create_info_struct(img->png);
 	if (!img->info)
-		errx(1, "Creating png info structure failed");
+		errx("Creating png info structure failed");
 
 	if (setjmp(png_jmpbuf(img->png)))
 		exit(1);
@@ -215,13 +215,13 @@
 		original_palette = palette;
 		palette = malloc(sizeof(*palette) * colors_in_PLTE);
 		if (!palette)
-			err(1, "%s: Failed to allocate memory for palette",
+			err("%s: Failed to allocate memory for palette",
 			    __func__);
 		colors_in_new_palette = 0;
 		old_to_new_palette = malloc(sizeof(*old_to_new_palette)
 					    * colors_in_PLTE);
 		if (!old_to_new_palette)
-			err(1, "%s: Failed to allocate memory for new palette",
+			err("%s: Failed to allocate memory for new palette",
 			    __func__);
 
 		for (i = 0; i < num_trans; i++) {
@@ -243,7 +243,7 @@
 					  sizeof(*palette) *
 					  colors_in_new_palette);
 			if (!palette)
-				err(1, "%s: Failed to allocate memory for palette",
+				err("%s: Failed to allocate memory for palette",
 				    __func__);
 		}
 
@@ -372,7 +372,7 @@
 	 */
 	*palette_ptr_ptr = calloc(colors, sizeof(**palette_ptr_ptr));
 	if (!*palette_ptr_ptr)
-		err(1, "%s: Failed to allocate memory for palette", __func__);
+		err("%s: Failed to allocate memory for palette", __func__);
 	palette = *palette_ptr_ptr;
 	*num_colors = 0;
 
@@ -429,7 +429,7 @@
 	}
 	if (!color_exists) {
 		if (*num_colors == colors) {
-			errx(1, "Too many colors in input PNG file to fit into a %d-bit palette (max %d).",
+			errx("Too many colors in input PNG file to fit into a %d-bit palette (max %d).",
 			     depth, colors);
 		}
 		palette[*num_colors] = *pixel_color;
@@ -445,9 +445,9 @@
 	int i, shade_index;
 
 	if (!fitted_palette)
-		err(1, "%s: Failed to allocate memory for palette", __func__);
+		err("%s: Failed to allocate memory for palette", __func__);
 	if (!set_indices)
-		err(1, "%s: Failed to allocate memory for indices", __func__);
+		err("%s: Failed to allocate memory for indices", __func__);
 
 	fitted_palette[0].red   = 0xFF;
 	fitted_palette[0].green = 0xFF;
@@ -508,7 +508,7 @@
 		malloc(sizeof(*palette_with_luminance) * num_colors);
 
 	if (!palette_with_luminance)
-		err(1, "%s: Failed to allocate memory for palette", __func__);
+		err("%s: Failed to allocate memory for palette", __func__);
 
 	for (i = 0; i < num_colors; i++) {
 		/*
@@ -600,7 +600,7 @@
 			return i;
 		}
 	}
-	errx(1, "The input PNG file contains colors that don't appear in its embedded palette.");
+	errx("The input PNG file contains colors that don't appear in its embedded palette.");
 }
 
 static void read_png(struct PNGImage *img)
@@ -611,12 +611,12 @@
 
 	img->data = malloc(sizeof(*img->data) * img->height);
 	if (!img->data)
-		err(1, "%s: Failed to allocate memory for image data",
+		err("%s: Failed to allocate memory for image data",
 		    __func__);
 	for (y = 0; y < img->height; y++) {
 		img->data[y] = malloc(png_get_rowbytes(img->png, img->info));
 		if (!img->data[y])
-			err(1, "%s: Failed to allocate memory for image data",
+			err("%s: Failed to allocate memory for image data",
 			    __func__);
 	}
 
@@ -632,7 +632,7 @@
 
 	raw_image = malloc(sizeof(*raw_image));
 	if (!raw_image)
-		err(1, "%s: Failed to allocate memory for raw image",
+		err("%s: Failed to allocate memory for raw image",
 		    __func__);
 
 	raw_image->width = width;
@@ -641,18 +641,18 @@
 
 	raw_image->palette = malloc(sizeof(*raw_image->palette) * num_colors);
 	if (!raw_image->palette)
-		err(1, "%s: Failed to allocate memory for raw image palette",
+		err("%s: Failed to allocate memory for raw image palette",
 		    __func__);
 
 	raw_image->data = malloc(sizeof(*raw_image->data) * height);
 	if (!raw_image->data)
-		err(1, "%s: Failed to allocate memory for raw image data",
+		err("%s: Failed to allocate memory for raw image data",
 		    __func__);
 	for (y = 0; y < height; y++) {
 		raw_image->data[y] = malloc(sizeof(*raw_image->data[y])
 					    * width);
 		if (!raw_image->data[y])
-			err(1, "%s: Failed to allocate memory for raw image data",
+			err("%s: Failed to allocate memory for raw image data",
 			    __func__);
 	}
 
@@ -665,7 +665,7 @@
 	int i;
 
 	if (num_colors > raw_image->num_colors) {
-		errx(1, "Too many colors in input PNG file's palette to fit into a %d-bit palette (%d in input palette, max %d).",
+		errx("Too many colors in input PNG file's palette to fit into a %d-bit palette (%d in input palette, max %d).",
 		     raw_image->num_colors >> 1,
 		     num_colors, raw_image->num_colors);
 	}
@@ -740,7 +740,7 @@
 
 	text = malloc(sizeof(*text));
 	if (!text)
-		err(1, "%s: Failed to allocate memory for PNG text",
+		err("%s: Failed to allocate memory for PNG text",
 		    __func__);
 
 	if (png_options->horizontal) {
--- a/src/hashmap.c
+++ b/src/hashmap.c
@@ -12,8 +12,8 @@
 #include <string.h>
 #include <assert.h>
 
+#include "error.h"
 #include "hashmap.h"
-#include "extern/err.h"
 
 /*
  * The lower half of the hash is used to index the "master" table,
@@ -53,7 +53,7 @@
 	struct HashMapEntry *newEntry = malloc(sizeof(*newEntry));
 
 	if (!newEntry)
-		err(1, "%s: Failed to allocate new entry", __func__);
+		err("%s: Failed to allocate new entry", __func__);
 
 	newEntry->hash = hashedKey >> HALF_HASH_NB_BITS;
 	newEntry->key = key;
--- a/src/link/assign.c
+++ b/src/link/assign.c
@@ -19,7 +19,7 @@
 #include "link/script.h"
 #include "link/output.h"
 
-#include "extern/err.h"
+#include "error.h"
 #include "helpers.h"
 
 struct MemoryLocation {
@@ -46,13 +46,13 @@
 	for (enum SectionType type = 0; type < SECTTYPE_INVALID; type++) {
 		memory[type] = malloc(sizeof(*memory[type]) * nbbanks(type));
 		if (!memory[type])
-			err(1, "Failed to init free space for region %d", type);
+			err("Failed to init free space for region %d", type);
 
 		for (uint32_t bank = 0; bank < nbbanks(type); bank++) {
 			memory[type][bank].next =
 				malloc(sizeof(*memory[type][0].next));
 			if (!memory[type][bank].next)
-				err(1, "Failed to init free space for region %d bank %" PRIu32,
+				err("Failed to init free space for region %d bank %" PRIu32,
 				    type, bank);
 			memory[type][bank].next->address = startaddr[type];
 			memory[type][bank].next->size    = maxsize[type];
@@ -301,7 +301,7 @@
 			struct FreeSpace *newSpace = malloc(sizeof(*newSpace));
 
 			if (!newSpace)
-				err(1, "Failed to split new free space");
+				err("Failed to split new free space");
 			/* Append the new space after the chosen one */
 			newSpace->prev = freeSpace;
 			newSpace->next = freeSpace->next;
@@ -352,16 +352,16 @@
 
 	/* If a section failed to go to several places, nothing we can report */
 	if (!section->isBankFixed || !section->isAddressFixed)
-		errx(1, "Unable to place \"%s\" (%s section) %s",
+		errx("Unable to place \"%s\" (%s section) %s",
 		     section->name, typeNames[section->type], where);
 	/* If the section just can't fit the bank, report that */
 	else if (section->org + section->size > endaddr(section->type) + 1)
-		errx(1, "Unable to place \"%s\" (%s section) %s: section runs past end of region ($%04x > $%04x)",
+		errx("Unable to place \"%s\" (%s section) %s: section runs past end of region ($%04x > $%04x)",
 		     section->name, typeNames[section->type], where,
 		     section->org + section->size, endaddr(section->type) + 1);
 	/* Otherwise there is overlap with another section */
 	else
-		errx(1, "Unable to place \"%s\" (%s section) %s: section overlaps with \"%s\"",
+		errx("Unable to place \"%s\" (%s section) %s: section overlaps with \"%s\"",
 		     section->name, typeNames[section->type], where,
 		     out_OverlappingSection(section)->name);
 }
@@ -418,7 +418,7 @@
 	/* Generate linked lists of sections to assign */
 	sections = malloc(sizeof(*sections) * nbSectionsToAssign + 1);
 	if (!sections)
-		err(1, "Failed to allocate memory for section assignment");
+		err("Failed to allocate memory for section assignment");
 
 	initFreeSpace();
 
@@ -447,7 +447,7 @@
 	/* Overlaying requires only fully-constrained sections */
 	verbosePrint("Assigning other sections...\n");
 	if (overlayFileName)
-		errx(1, "All sections must be fixed when using an overlay file; %" PRIu64 " %sn't",
+		errx("All sections must be fixed when using an overlay file; %" PRIu64 " %sn't",
 		     nbSectionsToAssign, nbSectionsToAssign == 1 ? "is" : "are");
 
 	/* Assign all remaining sections by decreasing constraint order */
--- a/src/link/main.c
+++ b/src/link/main.c
@@ -25,8 +25,9 @@
 #include "link/patch.h"
 #include "link/output.h"
 
-#include "extern/err.h"
 #include "extern/getopt.h"
+
+#include "error.h"
 #include "platform.h"
 #include "version.h"
 
@@ -156,7 +157,7 @@
 		file = fdopen(1, mode);
 
 	if (!file)
-		err(1, "Could not open file \"%s\"", fileName);
+		err("Could not open file \"%s\"", fileName);
 
 	return file;
 }
--- a/src/link/object.c
+++ b/src/link/object.c
@@ -21,7 +21,7 @@
 #include "link/section.h"
 #include "link/symbol.h"
 
-#include "extern/err.h"
+#include "error.h"
 #include "helpers.h"
 #include "linkdefs.h"
 
@@ -50,7 +50,7 @@
 		type tmpVal = func(tmpFile); \
 		/* TODO: maybe mark the condition as `unlikely`; how to do that portably? */ \
 		if (tmpVal == (errval)) { \
-			errx(1, __VA_ARGS__, feof(tmpFile) \
+			errx(__VA_ARGS__, feof(tmpFile) \
 						? "Unexpected end of file" \
 						: strerror(errno)); \
 		} \
@@ -289,13 +289,13 @@
 
 	patch->rpnExpression = malloc(sizeof(*patch->rpnExpression) * patch->rpnSize);
 	if (!patch->rpnExpression)
-		err(1, "%s: Failed to alloc \"%s\"'s patch #%" PRIu32 "'s RPN expression",
+		err("%s: Failed to alloc \"%s\"'s patch #%" PRIu32 "'s RPN expression",
 		    fileName, sectName, i);
 	size_t nbElementsRead = fread(patch->rpnExpression, sizeof(*patch->rpnExpression),
 				      patch->rpnSize, file);
 
 	if (nbElementsRead != patch->rpnSize)
-		errx(1, "%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s RPN expression: %s",
+		errx("%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s RPN expression: %s",
 		     fileName, sectName, i,
 		     feof(file) ? "Unexpected end of file" : strerror(errno));
 }
@@ -327,7 +327,7 @@
 	tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s' size: %s",
 		    fileName, section->name);
 	if (tmp < 0 || tmp > UINT16_MAX)
-		errx(1, "\"%s\"'s section size (%" PRId32 ") is invalid",
+		errx("\"%s\"'s section size (%" PRId32 ") is invalid",
 		     section->name, tmp);
 	section->size = tmp;
 	section->offset = 0;
@@ -373,13 +373,13 @@
 		uint8_t *data = malloc(sizeof(*data) * section->size + 1);
 
 		if (!data)
-			err(1, "%s: Unable to read \"%s\"'s data", fileName,
+			err("%s: Unable to read \"%s\"'s data", fileName,
 			    section->name);
 		if (section->size) {
 			size_t nbElementsRead = fread(data, sizeof(*data),
 						      section->size, file);
 			if (nbElementsRead != section->size)
-				errx(1, "%s: Cannot read \"%s\"'s data: %s",
+				errx("%s: Cannot read \"%s\"'s data: %s",
 				     fileName, section->name,
 				     feof(file) ? "Unexpected end of file"
 						: strerror(errno));
@@ -394,7 +394,7 @@
 			malloc(sizeof(*patches) * section->nbPatches + 1);
 
 		if (!patches)
-			err(1, "%s: Unable to read \"%s\"'s patches", fileName, section->name);
+			err("%s: Unable to read \"%s\"'s patches", fileName, section->name);
 		for (uint32_t i = 0; i < section->nbPatches; i++)
 			readPatch(file, &patches[i], fileName, section->name, i, fileNodes);
 		section->patches = patches;
@@ -462,7 +462,7 @@
 	FILE *file = strcmp("-", fileName) ? fopen(fileName, "rb") : stdin;
 
 	if (!file)
-		err(1, "Could not open file %s", fileName);
+		err("Could not open file %s", fileName);
 
 	/* Begin by reading the magic bytes and version number */
 	unsigned versionNumber;
@@ -470,13 +470,13 @@
 				  &versionNumber);
 
 	if (matchedElems != 1)
-		errx(1, "\"%s\" is not a RGBDS object file", fileName);
+		errx("\"%s\" is not a RGBDS object file", fileName);
 
 	verbosePrint("Reading object file %s, version %u\n",
 		     fileName, versionNumber);
 
 	if (versionNumber != RGBDS_OBJECT_VERSION_NUMBER)
-		errx(1, "\"%s\" is an incompatible version %u object file",
+		errx("\"%s\" is an incompatible version %u object file",
 		     fileName, versionNumber);
 
 	uint32_t revNum;
@@ -484,7 +484,7 @@
 	tryReadlong(revNum, file, "%s: Cannot read revision number: %s",
 		    fileName);
 	if (revNum != RGBDS_OBJECT_REV)
-		errx(1, "%s is a revision 0x%04" PRIx32 " object file; only 0x%04x is supported",
+		errx("%s is a revision 0x%04" PRIx32 " object file; only 0x%04x is supported",
 		     fileName, revNum, RGBDS_OBJECT_REV);
 
 	uint32_t nbSymbols;
@@ -500,7 +500,7 @@
 	tryReadlong(nodes[fileID].nbNodes, file, "%s: Cannot read number of nodes: %s", fileName);
 	nodes[fileID].nodes = calloc(nodes[fileID].nbNodes, sizeof(nodes[fileID].nodes[0]));
 	if (!nodes[fileID].nodes)
-		err(1, "Failed to get memory for %s's nodes", fileName);
+		err("Failed to get memory for %s's nodes", fileName);
 	verbosePrint("Reading %u nodes...\n", nodes[fileID].nbNodes);
 	for (uint32_t i = nodes[fileID].nbNodes; i--; )
 		readFileStackNode(file, nodes[fileID].nodes, i, fileName);
@@ -510,12 +510,12 @@
 		malloc(sizeof(*fileSymbols) * nbSymbols + 1);
 
 	if (!fileSymbols)
-		err(1, "Failed to get memory for %s's symbols", fileName);
+		err("Failed to get memory for %s's symbols", fileName);
 
 	struct SymbolList *symbolList = malloc(sizeof(*symbolList));
 
 	if (!symbolList)
-		err(1, "Failed to register %s's symbol list", fileName);
+		err("Failed to register %s's symbol list", fileName);
 	symbolList->symbolList = fileSymbols;
 	symbolList->nbSymbols = nbSymbols;
 	symbolList->next = symbolLists;
@@ -530,7 +530,7 @@
 		struct Symbol *symbol = malloc(sizeof(*symbol));
 
 		if (!symbol)
-			err(1, "%s: Couldn't create new symbol", fileName);
+			err("%s: Couldn't create new symbol", fileName);
 		readSymbol(file, symbol, fileName, nodes[fileID].nodes);
 
 		fileSymbols[i] = symbol;
@@ -549,7 +549,7 @@
 		/* Read section */
 		fileSections[i] = malloc(sizeof(*fileSections[i]));
 		if (!fileSections[i])
-			err(1, "%s: Couldn't create new section", fileName);
+			err("%s: Couldn't create new section", fileName);
 
 		fileSections[i]->nextu = NULL;
 		readSection(file, fileSections[i], fileName, nodes[fileID].nodes);
@@ -558,7 +558,7 @@
 			fileSections[i]->symbols = malloc(nbSymPerSect[i]
 					* sizeof(*fileSections[i]->symbols));
 			if (!fileSections[i]->symbols)
-				err(1, "%s: Couldn't link to symbols",
+				err("%s: Couldn't link to symbols",
 				    fileName);
 		} else {
 			fileSections[i]->symbols = NULL;
@@ -609,7 +609,7 @@
 		struct Assertion *assertion = malloc(sizeof(*assertion));
 
 		if (!assertion)
-			err(1, "%s: Couldn't create new assertion", fileName);
+			err("%s: Couldn't create new assertion", fileName);
 		readAssertion(file, assertion, fileName, i, nodes[fileID].nodes);
 		linkPatchToPCSect(&assertion->patch, fileSections);
 		assertion->fileSymbols = fileSymbols;
--- a/src/link/output.c
+++ b/src/link/output.c
@@ -16,10 +16,8 @@
 #include "link/section.h"
 #include "link/symbol.h"
 
-#include "extern/err.h"
-
+#include "error.h"
 #include "linkdefs.h"
-
 #include "platform.h" // MIN_NB_ELMS
 
 #define BANK_SIZE 0x4000
@@ -77,7 +75,7 @@
 	uint32_t minNbBanks = targetBank + 1;
 
 	if (minNbBanks > maxNbBanks[section->type])
-		errx(1, "Section \"%s\" has an invalid bank range (%" PRIu32 " > %" PRIu32 ")",
+		errx("Section \"%s\" has an invalid bank range (%" PRIu32 " > %" PRIu32 ")",
 		     section->name, section->bank,
 		     maxNbBanks[section->type] - 1);
 
@@ -92,7 +90,7 @@
 		sections[section->type].nbBanks = minNbBanks;
 	}
 	if (!sections[section->type].banks)
-		err(1, "Failed to realloc banks");
+		err("Failed to realloc banks");
 
 	struct SortedSection *newSection = malloc(sizeof(*newSection));
 	struct SortedSection **ptr = section->size
@@ -100,7 +98,7 @@
 		: &sections[section->type].banks[targetBank].zeroLenSections;
 
 	if (!newSection)
-		err(1, "Failed to add new section \"%s\"", section->name);
+		err("Failed to add new section \"%s\"", section->name);
 	newSection->section = section;
 
 	while (*ptr && (*ptr)->section->org < section->org)
@@ -145,15 +143,15 @@
 	fseek(overlayFile, 0, SEEK_SET);
 
 	if (overlaySize % BANK_SIZE)
-		errx(1, "Overlay file must have a size multiple of 0x4000");
+		errx("Overlay file must have a size multiple of 0x4000");
 
 	uint32_t nbOverlayBanks = overlaySize / BANK_SIZE;
 
 	if (is32kMode && nbOverlayBanks != 2)
-		errx(1, "Overlay must be exactly 0x8000 bytes large");
+		errx("Overlay must be exactly 0x8000 bytes large");
 
 	if (nbOverlayBanks < 2)
-		errx(1, "Overlay must be at least 0x8000 bytes large");
+		errx("Overlay must be at least 0x8000 bytes large");
 
 	return nbOverlayBanks;
 }
@@ -178,7 +176,7 @@
 			realloc(sections[SECTTYPE_ROMX].banks,
 				sizeof(*sections[SECTTYPE_ROMX].banks) * nbUncoveredBanks);
 		if (!sections[SECTTYPE_ROMX].banks)
-			err(1, "Failed to realloc banks for overlay");
+			err("Failed to realloc banks for overlay");
 		for (uint32_t i = sections[SECTTYPE_ROMX].nbBanks; i < nbUncoveredBanks; i++) {
 			sections[SECTTYPE_ROMX].banks[i].sections = NULL;
 			sections[SECTTYPE_ROMX].banks[i].zeroLenSections = NULL;
@@ -317,7 +315,7 @@
 	struct SortedSymbol *symList = malloc(sizeof(*symList) * nbSymbols);
 
 	if (!symList)
-		err(1, "Failed to allocate symbol list");
+		err("Failed to allocate symbol list");
 
 	uint32_t idx = 0;
 
--- a/src/link/patch.c
+++ b/src/link/patch.c
@@ -17,11 +17,10 @@
 #include "link/section.h"
 #include "link/symbol.h"
 
+#include "error.h"
 #include "linkdefs.h"
 #include "opmath.h"
 
-#include "extern/err.h"
-
 /*
  * This is an "empty"-type stack. Apart from the actual values, we also remember
  * whether the value is a placeholder inserted for error recovery. This allows
@@ -43,7 +42,7 @@
 	stack.values = malloc(sizeof(*stack.values) * stack.capacity);
 	stack.errorFlags = malloc(sizeof(*stack.errorFlags) * stack.capacity);
 	if (!stack.values || !stack.errorFlags)
-		err(1, "Failed to init RPN stack");
+		err("Failed to init RPN stack");
 }
 
 static void clearRPNStack(void)
@@ -57,7 +56,7 @@
 		static const size_t increase_factor = 2;
 
 		if (stack.capacity > SIZE_MAX / increase_factor)
-			errx(1, "Overflow in RPN stack resize");
+			errx("Overflow in RPN stack resize");
 
 		stack.capacity *= increase_factor;
 		stack.values =
@@ -70,7 +69,7 @@
 		 * the overflow check above. Hence the stringent check below.
 		 */
 		if (!stack.values || !stack.errorFlags || !stack.capacity)
-			err(1, "Failed to resize RPN stack");
+			err("Failed to resize RPN stack");
 	}
 
 	stack.values[stack.size] = value;
--- a/src/link/script.c
+++ b/src/link/script.c
@@ -17,7 +17,7 @@
 #include "link/script.h"
 #include "link/section.h"
 
-#include "extern/err.h"
+#include "error.h"
 
 FILE *linkerScript;
 char *includeFileName;
@@ -36,7 +36,7 @@
 static void pushFile(char *newFileName)
 {
 	if (fileStackIndex == UINT32_MAX)
-		errx(1, "%s(%" PRIu32 "): INCLUDE recursion limit reached",
+		errx("%s(%" PRIu32 "): INCLUDE recursion limit reached",
 		     linkerScriptName, lineNo);
 
 	if (fileStackIndex == fileStackSize) {
@@ -45,7 +45,7 @@
 		fileStackSize *= 2;
 		fileStack = realloc(fileStack, sizeof(*fileStack) * fileStackSize);
 		if (!fileStack)
-			err(1, "%s(%" PRIu32 "): Internal INCLUDE error",
+			err("%s(%" PRIu32 "): Internal INCLUDE error",
 			    linkerScriptName, lineNo);
 	}
 
@@ -56,7 +56,7 @@
 
 	linkerScript = fopen(newFileName, "r");
 	if (!linkerScript)
-		err(1, "%s(%" PRIu32 "): Could not open \"%s\"",
+		err("%s(%" PRIu32 "): Could not open \"%s\"",
 		    linkerScriptName, lineNo, newFileName);
 	lineNo = 1;
 	linkerScriptName = newFileName;
@@ -177,7 +177,7 @@
 	int curchar = getc(linkerScript);
 
 	if (curchar == EOF && ferror(linkerScript))
-		err(1, "%s(%" PRIu32 "): Unexpected error in %s",
+		err("%s(%" PRIu32 "): Unexpected error in %s",
 		    linkerScriptName, lineNo, __func__);
 	return curchar;
 }
@@ -226,7 +226,7 @@
 		do {
 			curchar = nextChar();
 			if (curchar == EOF || isNewline(curchar)) {
-				errx(1, "%s(%" PRIu32 "): Unterminated string",
+				errx("%s(%" PRIu32 "): Unterminated string",
 				     linkerScriptName, lineNo);
 			} else if (curchar == '"') {
 				/* Quotes force a string termination */
@@ -235,7 +235,7 @@
 				/* Backslashes are escape sequences */
 				curchar = nextChar();
 				if (curchar == EOF || isNewline(curchar))
-					errx(1, "%s(%" PRIu32 "): Unterminated string",
+					errx("%s(%" PRIu32 "): Unterminated string",
 					     linkerScriptName, lineNo);
 				else if (curchar == 'n')
 					curchar = '\n';
@@ -244,7 +244,7 @@
 				else if (curchar == 't')
 					curchar = '\t';
 				else if (curchar != '\\' && curchar != '"')
-					errx(1, "%s(%" PRIu32 "): Illegal character escape",
+					errx("%s(%" PRIu32 "): Illegal character escape",
 					     linkerScriptName, lineNo);
 			}
 
@@ -252,7 +252,7 @@
 				capacity *= 2;
 				token.attr.string = realloc(token.attr.string, capacity);
 				if (!token.attr.string)
-					err(1, "%s: Failed to allocate memory for string",
+					err("%s: Failed to allocate memory for string",
 					    __func__);
 			}
 			token.attr.string[size++] = curchar;
@@ -268,7 +268,7 @@
 				capacity *= 2;
 				str = realloc(str, capacity);
 				if (!str)
-					err(1, "%s: Failed to allocate memory for token",
+					err("%s: Failed to allocate memory for token",
 					    __func__);
 			}
 			str[size] = toupper(curchar);
@@ -318,7 +318,7 @@
 			if (tryParseNumber(str, &token.attr.number))
 				token.type = TOKEN_NUMBER;
 			else
-				errx(1, "%s(%" PRIu32 "): Unknown token \"%s\"",
+				errx("%s(%" PRIu32 "): Unknown token \"%s\"",
 				     linkerScriptName, lineNo, str);
 		}
 
@@ -345,7 +345,7 @@
 	}
 
 	if (arg < *pc)
-		errx(1, "%s(%" PRIu32 "): `%s` cannot be used to go backwards (currently at $%x)",
+		errx("%s(%" PRIu32 "): `%s` cannot be used to go backwards (currently at $%x)",
 		     linkerScriptName, lineNo, commands[command], *pc);
 	*pc = arg;
 }
@@ -394,11 +394,11 @@
 
 		if (type != SECTTYPE_INVALID) {
 			if (curaddr[type][bankID] > endaddr(type) + 1)
-				errx(1, "%s(%" PRIu32 "): Sections would extend past the end of %s ($%04" PRIx16 " > $%04" PRIx16 ")",
+				errx("%s(%" PRIu32 "): Sections would extend past the end of %s ($%04" PRIx16 " > $%04" PRIx16 ")",
 				     linkerScriptName, lineNo, typeNames[type],
 				     curaddr[type][bankID], endaddr(type));
 			if (curaddr[type][bankID] < startaddr[type])
-				errx(1, "%s(%" PRIu32 "): PC underflowed ($%04" PRIx16 " < $%04" PRIx16 ")",
+				errx("%s(%" PRIu32 "): PC underflowed ($%04" PRIx16 " < $%04" PRIx16 ")",
 				     linkerScriptName, lineNo,
 				     curaddr[type][bankID], startaddr[type]);
 		}
@@ -419,7 +419,7 @@
 				break;
 
 			case TOKEN_NUMBER:
-				errx(1, "%s(%" PRIu32 "): stray number \"%" PRIu32 "\"",
+				errx("%s(%" PRIu32 "): stray number \"%" PRIu32 "\"",
 				     linkerScriptName, lineNo,
 				     token->attr.number);
 
@@ -432,13 +432,13 @@
 				parserState = PARSER_LINEEND;
 
 				if (type == SECTTYPE_INVALID)
-					errx(1, "%s(%" PRIu32 "): Didn't specify a location before the section",
+					errx("%s(%" PRIu32 "): Didn't specify a location before the section",
 					     linkerScriptName, lineNo);
 
 				section.section =
 					sect_GetSection(token->attr.string);
 				if (!section.section)
-					errx(1, "%s(%" PRIu32 "): Unknown section \"%s\"",
+					errx("%s(%" PRIu32 "): Unknown section \"%s\"",
 					     linkerScriptName, lineNo,
 					     token->attr.string);
 				section.org = curaddr[type][bankID];
@@ -467,10 +467,10 @@
 
 				if (tokType == TOKEN_COMMAND) {
 					if (type == SECTTYPE_INVALID)
-						errx(1, "%s(%" PRIu32 "): Didn't specify a location before the command",
+						errx("%s(%" PRIu32 "): Didn't specify a location before the command",
 						     linkerScriptName, lineNo);
 					if (!hasArg)
-						errx(1, "%s(%" PRIu32 "): Command specified without an argument",
+						errx("%s(%" PRIu32 "): Command specified without an argument",
 						     linkerScriptName, lineNo);
 
 					processCommand(attr.command, arg, &curaddr[type][bankID]);
@@ -481,16 +481,16 @@
 					 * specifying the number is optional.
 					 */
 					if (!hasArg && nbbanks(type) != 1)
-						errx(1, "%s(%" PRIu32 "): Didn't specify a bank number",
+						errx("%s(%" PRIu32 "): Didn't specify a bank number",
 						     linkerScriptName, lineNo);
 					else if (!hasArg)
 						arg = bankranges[type][0];
 					else if (arg < bankranges[type][0])
-						errx(1, "%s(%" PRIu32 "): specified bank number is too low (%" PRIu32 " < %" PRIu32 ")",
+						errx("%s(%" PRIu32 "): specified bank number is too low (%" PRIu32 " < %" PRIu32 ")",
 						     linkerScriptName, lineNo,
 						     arg, bankranges[type][0]);
 					else if (arg > bankranges[type][1])
-						errx(1, "%s(%" PRIu32 "): specified bank number is too high (%" PRIu32 " > %" PRIu32 ")",
+						errx("%s(%" PRIu32 "): specified bank number is too high (%" PRIu32 " > %" PRIu32 ")",
 						     linkerScriptName, lineNo,
 						     arg, bankranges[type][1]);
 					bank = arg;
@@ -510,7 +510,7 @@
 
 		case PARSER_INCLUDE:
 			if (token->type != TOKEN_STRING)
-				errx(1, "%s(%" PRIu32 "): Expected a file name after INCLUDE",
+				errx("%s(%" PRIu32 "): Expected a file name after INCLUDE",
 				     linkerScriptName, lineNo);
 
 			/* Switch to that file */
@@ -530,7 +530,7 @@
 					return NULL;
 				parserState = PARSER_LINEEND;
 			} else if (token->type != TOKEN_NEWLINE)
-				errx(1, "%s(%" PRIu32 "): Unexpected %s at the end of the line",
+				errx("%s(%" PRIu32 "): Unexpected %s at the end of the line",
 				     linkerScriptName, lineNo,
 				     tokenTypes[token->type]);
 			break;
--- a/src/link/section.c
+++ b/src/link/section.c
@@ -14,8 +14,7 @@
 #include "link/main.h"
 #include "link/section.h"
 
-#include "extern/err.h"
-
+#include "error.h"
 #include "hashmap.h"
 
 HashMap sections;
@@ -44,12 +43,12 @@
 	if (other->isAddressFixed) {
 		if (target->isAddressFixed) {
 			if (target->org != other->org)
-				errx(1, "Section \"%s\" is defined with conflicting addresses $%04"
+				errx("Section \"%s\" is defined with conflicting addresses $%04"
 				     PRIx16 " and $%04" PRIx16,
 				     other->name, target->org, other->org);
 		} else if (target->isAlignFixed) {
 			if ((other->org - target->alignOfs) & target->alignMask)
-				errx(1, "Section \"%s\" is defined with conflicting %d-byte alignment (offset %"
+				errx("Section \"%s\" is defined with conflicting %d-byte alignment (offset %"
 				     PRIu16 ") and address $%04" PRIx16,
 				     other->name, target->alignMask + 1,
 				     target->alignOfs, other->org);
@@ -60,7 +59,7 @@
 	} else if (other->isAlignFixed) {
 		if (target->isAddressFixed) {
 			if ((target->org - other->alignOfs) & other->alignMask)
-				errx(1, "Section \"%s\" is defined with conflicting address $%04"
+				errx("Section \"%s\" is defined with conflicting address $%04"
 				     PRIx16 " and %d-byte alignment (offset %" PRIu16 ")",
 				     other->name, target->org,
 				     other->alignMask + 1, other->alignOfs);
@@ -67,7 +66,7 @@
 		} else if (target->isAlignFixed
 			&& (other->alignMask & target->alignOfs)
 				 != (target->alignMask & other->alignOfs)) {
-			errx(1, "Section \"%s\" is defined with conflicting %d-byte alignment (offset %"
+			errx("Section \"%s\" is defined with conflicting %d-byte alignment (offset %"
 			     PRIu16 ") and %d-byte alignment (offset %" PRIu16 ")",
 			     other->name, target->alignMask + 1, target->alignOfs,
 			     other->alignMask + 1, other->alignOfs);
@@ -85,13 +84,13 @@
 
 		if (target->isAddressFixed) {
 			if (target->org != org)
-				errx(1, "Section \"%s\" is defined with conflicting addresses $%04"
+				errx("Section \"%s\" is defined with conflicting addresses $%04"
 				     PRIx16 " and $%04" PRIx16,
 				     other->name, target->org, other->org);
 
 		} else if (target->isAlignFixed) {
 			if ((org - target->alignOfs) & target->alignMask)
-				errx(1, "Section \"%s\" is defined with conflicting %d-byte alignment (offset %"
+				errx("Section \"%s\" is defined with conflicting %d-byte alignment (offset %"
 				     PRIu16 ") and address $%04" PRIx16,
 				     other->name, target->alignMask + 1,
 				     target->alignOfs, other->org);
@@ -107,7 +106,7 @@
 
 		if (target->isAddressFixed) {
 			if ((target->org - ofs) & other->alignMask)
-				errx(1, "Section \"%s\" is defined with conflicting address $%04"
+				errx("Section \"%s\" is defined with conflicting address $%04"
 				     PRIx16 " and %d-byte alignment (offset %" PRIu16 ")",
 				     other->name, target->org,
 				     other->alignMask + 1, other->alignOfs);
@@ -114,7 +113,7 @@
 
 		} else if (target->isAlignFixed
 			&& (other->alignMask & target->alignOfs) != (target->alignMask & ofs)) {
-			errx(1, "Section \"%s\" is defined with conflicting %d-byte alignment (offset %"
+			errx("Section \"%s\" is defined with conflicting %d-byte alignment (offset %"
 			     PRIu16 ") and %d-byte alignment (offset %" PRIu16 ")",
 			     other->name, target->alignMask + 1, target->alignOfs,
 			     other->alignMask + 1, other->alignOfs);
@@ -132,7 +131,7 @@
 	// Common checks
 
 	if (target->type != other->type)
-		errx(1, "Section \"%s\" is defined with conflicting types %s and %s",
+		errx("Section \"%s\" is defined with conflicting types %s and %s",
 		     other->name, typeNames[target->type], typeNames[other->type]);
 
 	if (other->isBankFixed) {
@@ -140,7 +139,7 @@
 			target->isBankFixed = true;
 			target->bank = other->bank;
 		} else if (target->bank != other->bank) {
-			errx(1, "Section \"%s\" is defined with conflicting banks %" PRIu32 " and %"
+			errx("Section \"%s\" is defined with conflicting banks %" PRIu32 " and %"
 			     PRIu32, other->name, target->bank, other->bank);
 		}
 	}
@@ -161,7 +160,7 @@
 			target->data = realloc(target->data,
 					       sizeof(*target->data) * target->size + 1);
 			if (!target->data)
-				errx(1, "Failed to concatenate \"%s\"'s fragments", target->name);
+				errx("Failed to concatenate \"%s\"'s fragments", target->name);
 			memcpy(target->data + target->size - other->size, other->data, other->size);
 			/* Adjust patches' PC offsets */
 			for (uint32_t patchID = 0; patchID < other->nbPatches; patchID++)
@@ -184,14 +183,14 @@
 
 	if (other) {
 		if (section->modifier != other->modifier)
-			errx(1, "Section \"%s\" defined as %s and %s", section->name,
+			errx("Section \"%s\" defined as %s and %s", section->name,
 			     sectionModNames[section->modifier], sectionModNames[other->modifier]);
 		else if (section->modifier == SECTION_NORMAL)
-			errx(1, "Section name \"%s\" is already in use", section->name);
+			errx("Section name \"%s\" is already in use", section->name);
 		else
 			mergeSections(other, section, section->modifier);
 	} else if (section->modifier == SECTION_UNION && sect_HasData(section->type)) {
-		errx(1, "Section \"%s\" is of type %s, which cannot be unionized",
+		errx("Section \"%s\" is of type %s, which cannot be unionized",
 		     section->name, typeNames[section->type]);
 	} else {
 		/* If not, add it */
@@ -302,5 +301,5 @@
 {
 	sect_ForEach(doSanityChecks, NULL);
 	if (sanityChecksFailed)
-		errx(1, "Sanity checks failed");
+		errx("Sanity checks failed");
 }
--- a/src/link/symbol.c
+++ b/src/link/symbol.c
@@ -14,7 +14,7 @@
 #include "link/symbol.h"
 #include "link/main.h"
 
-#include "extern/err.h"
+#include "error.h"
 #include "hashmap.h"
 
 HashMap symbols;