This commit is contained in:
Aviral Garg 2026-07-30 20:31:08 +05:30 committed by GitHub
commit 480c6b6e1a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 437 additions and 39 deletions

View file

@ -4,7 +4,7 @@
#
config GAMES_NXDOOM
bool "NXDoom"
tristate "NXDoom"
default n
depends on ALLOW_GPL_COMPONENTS
depends on VIDEO_FB
@ -197,6 +197,27 @@ config GAMES_NXDOOM_MAXDRAWSEGS
memory, so you may reduce the number. However, too few will cause
rendering issues (overflow is checked to avoid crashes).
config GAMES_NXDOOM_HEAP_BUFFERS
bool "Allocate renderer scratch buffers on the heap"
default n
---help---
The visplanes/openings/drawsegs/vissprites renderer scratch buffers
(sized by the options above) are static arrays by default, matching
vanilla DOOM. On a target where their combined size threatens the
internal DRAM budget once linked into a full application image,
enable this to allocate them from the heap instead (this target's
heap may be backed by external RAM/PSRAM). Static allocation is
preferred where DRAM budget is not a concern.
config GAMES_NXDOOM_STATDUMP_MAX_CAPTURES
int "Maximum statdump capture buffer entries"
default 32
range 1 1024
---help---
Number of playtime-statistics capture slots statdump.c reserves.
This is diagnostic/debug capture storage, not required for normal
gameplay - reduce it on a DRAM-constrained target.
config GAMES_NXDOOM_RANGECHECK
bool "Perform range checks"
default y

View file

@ -271,6 +271,16 @@ static void buld_iwad_dir_list(void)
add_iwad_dir(m_dir_name(myargv[0]));
/* Add the board's configured DOOM data directory. Kconfig documents
* CONFIG_GAMES_NXDOOM_PREFDIR as "Directory where DOOM WAD files are
* stored", but until now it was only used for the config/save file
* location -- nothing actually searched it for IWADs, forcing every
* launch to rely on the current directory or DOOMWADDIR/DOOMWADPATH
* being set by hand first.
*/
add_iwad_dir(CONFIG_GAMES_NXDOOM_PREFDIR);
/* Add DOOMWADDIR if it is in the environment */
env = getenv("DOOMWADDIR");

View file

@ -1294,6 +1294,12 @@ void d_doomloop(void)
while (1)
{
/* Safe point (outside any framebuffer/heap access) for nxstore's
* SIGTERM-driven close request to actually take effect - see
* i_install_quit_signal() in i_system.h.
*/
i_poll_quit_signal();
d_run_frame();
}
}

View file

@ -78,7 +78,11 @@ line_t *linedef;
sector_t *frontsector;
sector_t *backsector;
#ifdef CONFIG_GAMES_NXDOOM_HEAP_BUFFERS
drawseg_t *drawsegs;
#else
drawseg_t drawsegs[CONFIG_GAMES_NXDOOM_MAXDRAWSEGS];
#endif
drawseg_t *ds_p;
/* newend is one past the last valid seg */

View file

@ -52,7 +52,11 @@ extern boolean markceiling;
extern boolean skymap;
#ifdef CONFIG_GAMES_NXDOOM_HEAP_BUFFERS
extern drawseg_t *drawsegs;
#else
extern drawseg_t drawsegs[CONFIG_GAMES_NXDOOM_MAXDRAWSEGS];
#endif
extern drawseg_t *ds_p;
extern lighttable_t **hscalelight;

View file

@ -597,7 +597,7 @@ void r_draw_span(void)
#ifdef CONFIG_GAMES_NXDOOM_RANGECHECK
if (ds_x2 < ds_x1 || ds_x1 < 0 || ds_x2 >= SCREENWIDTH ||
(unsigned)ds_y > SCREENHEIGHT)
ds_y < 0 || ds_y >= viewheight)
{
i_error("r_draw_span: %i to %i at %i", ds_x1, ds_x2, ds_y);
}
@ -724,7 +724,7 @@ void r_draw_span_low(void)
#ifdef CONFIG_GAMES_NXDOOM_RANGECHECK
if (ds_x2 < ds_x1 || ds_x1 < 0 || ds_x2 >= SCREENWIDTH ||
(unsigned)ds_y > SCREENHEIGHT)
ds_y < 0 || ds_y >= viewheight)
{
i_error("r_draw_span: %i to %i at %i", ds_x1, ds_x2, ds_y);
}

View file

@ -685,6 +685,24 @@ fixed_t r_scale_from_global_angle(angle_t visangle)
void r_set_view_size(int blocks, int detail)
{
/* screenblocks is only ever meant to hold 3..11 (set that way by the
* options menu and by the config default of 9). The renderer's view
* geometry math divides by values derived from it - notably
* pspriteiscale = FRACUNIT * SCREENWIDTH / viewwidth in
* r_execute_set_view_size() - so a 0 or otherwise out-of-range value
* turns into a divide-by-zero hardware exception (EXCCAUSE=6), which on
* this flat-memory build takes the whole board down rather than just
* this task. Clamp defensively so a bad/missing config value degrades
* to the default screen size instead of a system crash.
*/
if (blocks < 3 || blocks > 11)
{
printf("r_set_view_size: screenblocks=%d out of range, using 10\n",
blocks);
blocks = 10;
}
setsizeneeded = true;
setblocks = blocks;
setdetail = detail;

View file

@ -57,12 +57,17 @@ planefunction_t ceilingfunc;
/* Here comes the obnoxious "visplane". */
#ifdef CONFIG_GAMES_NXDOOM_HEAP_BUFFERS
visplane_t *visplanes;
short *openings;
#else
visplane_t visplanes[CONFIG_GAMES_NXDOOM_MAXVISPLANES];
short openings[MAXOPENINGS];
#endif
visplane_t *lastvisplane;
visplane_t *floorplane;
visplane_t *ceilingplane;
short openings[MAXOPENINGS];
short *lastopening;
/* Clip values are the solid pixel bounding the range. floorclip starts out
@ -114,12 +119,46 @@ static void r_map_plane(int y, int x1, int x2)
fixed_t length;
unsigned index;
#ifdef CONFIG_GAMES_NXDOOM_RANGECHECK
if (x2 < x1 || x1 < 0 || x2 >= viewwidth || y > viewheight)
/* y indexes cachedheight[]/cacheddistance[]/cachedxstep[]/cachedystep[]
* below, all sized SCREENHEIGHT - a y outside that range (observed on
* this port: y=255 against a 200-entry array, well past even
* viewheight) is an out-of-bounds array write, not just a "debug
* assertion". This used to be gated behind CONFIG_GAMES_NXDOOM_
* RANGECHECK and fatal (i_error(), which tears down the whole process
* on what vanilla Doom would just render as one glitched span) - both
* wrong: the memory-safety check must not be optional, and killing the
* entire game over one bad plane span is worse than just not drawing
* it. Clamp y into range instead of touching memory outside the
* buffers' real bounds - this still renders the span (as one glitched
* row, the same "wrong but visible" failure mode vanilla DOOM has) so
* a bad plane doesn't leave a blank gap on screen either.
*
* The clamp bound must be viewheight, not SCREENHEIGHT: this y is
* stored into ds_y and later used by r_draw_span() to index
* ylookup[] (r_draw.c), which r_init_buffer() only populates for
* [0, viewheight) - viewheight can be smaller than SCREENHEIGHT (a
* sub-window within the physical screen), so entries from viewheight
* up to SCREENHEIGHT are zero-initialized (NULL) pointers. Clamping
* to SCREENHEIGHT - 1 instead of viewheight - 1 traded the original
* out-of-bounds write for a NULL-pointer-plus-offset framebuffer
* write - confirmed on real hardware as a load/store exception at a
* small virtual address. viewheight is always <= SCREENHEIGHT, so
* this bound is safe for cachedheight[]/etc. too.
*/
if (x2 < x1 || x1 < 0 || x2 >= viewwidth)
{
i_error("R_MapPlane: %i, %i at %i", x1, x2, y);
return;
}
if (y < 0)
{
y = 0;
}
else if (y >= viewheight)
{
y = viewheight - 1;
}
#endif
if (planeheight != cachedheight[y])
{
@ -160,27 +199,62 @@ static void r_map_plane(int y, int x1, int x2)
spanfunc();
}
/* Row indices into spanstart[] (sized SCREENHEIGHT) that are only ever
* safe to use as an array index within that range - t1/b1/t2/b2 in
* r_make_spans() below are also compared directly against each other to
* drive the span-tracking state machine (including vanilla DOOM's 0xff
* sentinel for "no span"/edge-of-plane), and that comparison logic must
* see the real, un-clamped values or the sentinel handling breaks. Only
* the array touches themselves need guarding.
*/
static inline boolean r_row_in_range(int row)
{
return row >= 0 && row < SCREENHEIGHT;
}
static void r_make_spans(int x, int t1, int b1, int t2, int b2)
{
/* t1/b1/t2/b2 come from a visplane's top[]/bottom[] arrays. In valid
* play these are either a real screen row or vanilla DOOM's 0xff
* (255) "no span here" sentinel; the loop conditions normally keep
* that sentinel away from spanstart[]. A malformed renderer state
* can violate that invariant, however: row 255 was observed reaching
* r_map_plane() on real hardware, after spanstart[t1]/[b1] had already
* been evaluated as the call argument. Guard every spanstart[] touch
* directly instead of altering t1/b1/t2/b2, so the state-machine
* comparisons and normal sentinel handling remain unchanged. An
* invalid closing row uses column zero as its bounded fallback; an
* invalid opening row is ignored.
*/
while (t1 < t2 && t1 <= b1)
{
r_map_plane(t1, spanstart[t1], x - 1);
r_map_plane(t1, r_row_in_range(t1) ? spanstart[t1] : 0, x - 1);
t1++;
}
while (b1 > b2 && b1 >= t1)
{
r_map_plane(b1, spanstart[b1], x - 1);
r_map_plane(b1, r_row_in_range(b1) ? spanstart[b1] : 0, x - 1);
b1--;
}
while (t2 < t1 && t2 <= b2)
{
spanstart[t2] = x;
if (r_row_in_range(t2))
{
spanstart[t2] = x;
}
t2++;
}
while (b2 > b1 && b2 >= t2)
{
spanstart[b2] = x;
if (r_row_in_range(b2))
{
spanstart[b2] = x;
}
b2--;
}
}
@ -195,7 +269,70 @@ static void r_make_spans(int x, int t1, int b1, int t2, int b2)
void r_init_planes(void)
{
/* Doh! */
#ifdef CONFIG_GAMES_NXDOOM_HEAP_BUFFERS
/* These renderer scratch buffers are sized for a comfortable margin
* above vanilla DOOM's original limits and, on a DRAM-constrained
* target, blow the internal DRAM budget as static arrays - opt-in
* heap allocation instead (comes out of the PSRAM-backed user heap
* on this target) via CONFIG_GAMES_NXDOOM_HEAP_BUFFERS.
*/
visplanes = malloc(sizeof(visplane_t) * CONFIG_GAMES_NXDOOM_MAXVISPLANES);
openings = malloc(sizeof(short) * MAXOPENINGS);
drawsegs = malloc(sizeof(drawseg_t) * CONFIG_GAMES_NXDOOM_MAXDRAWSEGS);
vissprites = malloc(sizeof(vissprite_t) *
CONFIG_GAMES_NXDOOM_MAXVISSPRITES);
if (visplanes == NULL || openings == NULL || drawsegs == NULL ||
vissprites == NULL)
{
/* i_error() doesn't necessarily terminate the whole board on this
* flat, single address-space build (see the comment below on
* relaunch) - free whatever partially succeeded so a failed
* allocation attempt doesn't leak across a subsequent relaunch.
*/
free(visplanes);
free(openings);
free(drawsegs);
free(vissprites);
visplanes = NULL;
openings = NULL;
drawsegs = NULL;
vissprites = NULL;
i_error("r_init_planes: failed to allocate renderer buffers");
}
/* i_quit() can be followed by another r_init_planes() call within the
* same boot (relaunching the game via nxpkg on this flat, single
* address-space build), so these heap buffers must be freed on exit
* or every relaunch leaks the previous allocation permanently.
*/
i_at_exit(r_shutdown_planes, true);
#endif
}
/* r_shutdown_planes
* Frees the renderer scratch buffers allocated by r_init_planes. Only
* registered as an exit handler when CONFIG_GAMES_NXDOOM_HEAP_BUFFERS is
* set - the static-array buffers have nothing to free.
*/
void r_shutdown_planes(void)
{
#ifdef CONFIG_GAMES_NXDOOM_HEAP_BUFFERS
free(visplanes);
free(openings);
free(drawsegs);
free(vissprites);
visplanes = NULL;
openings = NULL;
drawsegs = NULL;
vissprites = NULL;
#endif
}
/* r_clear_planes
@ -314,13 +451,15 @@ visplane_t *r_check_plane(visplane_t *pl, int start, int stop)
/* make a new visplane */
if (lastvisplane - visplanes == CONFIG_GAMES_NXDOOM_MAXVISPLANES)
{
i_error("r_check_plane: no more visplanes");
}
lastvisplane->height = pl->height;
lastvisplane->picnum = pl->picnum;
lastvisplane->lightlevel = pl->lightlevel;
if (lastvisplane - visplanes == CONFIG_GAMES_NXDOOM_MAXVISPLANES)
i_error("r_check_plane: no more visplanes");
pl = lastvisplane++;
pl->minx = start;
pl->maxx = stop;

View file

@ -58,6 +58,7 @@ extern fixed_t distscale[SCREENWIDTH];
****************************************************************************/
void r_init_planes(void);
void r_shutdown_planes(void);
void r_clear_planes(void);
void r_draw_planes(void);

View file

@ -93,7 +93,11 @@ spriteframe_t sprtemp[29];
int maxframe;
const char *spritename;
#ifdef CONFIG_GAMES_NXDOOM_HEAP_BUFFERS
vissprite_t *vissprites;
#else
vissprite_t vissprites[CONFIG_GAMES_NXDOOM_MAXVISSPRITES];
#endif
vissprite_t *vissprite_p;
int newvissprite;

View file

@ -28,7 +28,11 @@
* Public Data
****************************************************************************/
#ifdef CONFIG_GAMES_NXDOOM_HEAP_BUFFERS
extern vissprite_t *vissprites;
#else
extern vissprite_t vissprites[CONFIG_GAMES_NXDOOM_MAXVISSPRITES];
#endif
extern vissprite_t *vissprite_p;
extern vissprite_t vsprsortedhead;

View file

@ -39,7 +39,7 @@
* Pre-processor Definitions
****************************************************************************/
#define MAX_CAPTURES 32
#define MAX_CAPTURES CONFIG_GAMES_NXDOOM_STATDUMP_MAX_CAPTURES
/****************************************************************************
* Private Data

View file

@ -57,6 +57,15 @@ void d_doom_main(void);
int main(int argc, char **argv)
{
/* Lets nxstore (or any other supervisor) ask this process to exit
* cleanly via SIGTERM instead of the only other option being a forced
* task_delete() from outside - see i_system.h/i_system.c for why that
* matters on this board (a forced kill mid framebuffer/heap access was
* observed to hang the whole system, not just this task).
*/
i_install_quit_signal();
/* save arguments */
myargc = argc;

View file

@ -22,10 +22,13 @@
* Included Files
****************************************************************************/
#include <errno.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include "config.h"
@ -75,6 +78,15 @@ static atexit_listentry_t *exit_funcs = NULL;
static boolean already_quitting = false;
/* Set only by i_quit_signal_handler() (async-signal-safe: a single
* sig_atomic_t store, nothing else) and read only by
* i_poll_quit_signal(), called from a safe point in the main loop - see
* the comment on i_install_quit_signal() in i_system.h for why the
* actual i_quit() cleanup is deferred out of the signal handler itself.
*/
static volatile sig_atomic_t quit_requested = 0;
/* Read Access Violation emulation.
*
* From PrBoom+, by entryway.
@ -320,6 +332,71 @@ void i_quit(void)
exit(0);
}
/* i_quit_signal_handler
*
* A supervisor process (nxstore) has no reachable in-game quit path to
* drive (no keyboard/touch input is wired up here) - it can only ask
* from the outside, via SIGTERM. This handler does the one thing a
* signal handler is safe to do: set a flag. It must NOT call i_quit()
* (or anything it does - munmap, fclose, exit()'s atexit chain) directly,
* because a signal can land at literally any point in this process's own
* execution, including mid-malloc()/mid-blit - exactly the same "unsafe
* mid-operation teardown" risk as being force-killed from outside, just
* moved from another task's context into this one. i_poll_quit_signal()
* defers the real work to a known-safe boundary instead.
*/
static void i_quit_signal_handler(int signo)
{
(void)signo;
quit_requested = 1;
}
void i_install_quit_signal(void)
{
struct sigaction sa;
/* This board's flat, single address-space build can relaunch NXDoom
* (via nxpkg) as a fresh loadable ELF module - a proper posix_spawn of
* a new module load, which gets its own zeroed .bss/re-initialized
* .data - but GAMES_NXDOOM is a tristate Kconfig symbol and can also
* be built in as a true built-in (MODULE=n) sharing this process's
* address space across "launches" with no fresh .bss at all. Reset
* both pieces of state a stale second invocation could see: a leaked
* quit_requested flag would call i_quit() again before the game even
* starts, and a leaked exit_funcs chain would run every previous
* invocation's exit handlers a second time (double free()s, etc.) in
* addition to this invocation's own.
*/
quit_requested = 0;
exit_funcs = NULL;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = i_quit_signal_handler;
if (sigaction(SIGTERM, &sa, NULL) < 0)
{
/* Not fatal - the game still runs, it just can't be asked to
* close cleanly from the outside (nxstore's close button will
* have nothing to signal into). Surface it rather than silently
* leaving close non-functional with no trace of why.
*/
syslog(LOG_WARNING,
"nxdoom: failed to install SIGTERM handler: %d\n", errno);
}
}
void i_poll_quit_signal(void)
{
if (quit_requested)
{
syslog(LOG_WARNING, "nxdoom: quit signal seen, calling i_quit\n");
i_quit();
}
}
void i_error(const char *error, ...)
{
char msgbuf[512];

View file

@ -73,6 +73,23 @@ ticcmd_t *i_base_ticcmd(void);
void i_quit(void) NORETURN;
/* Installs a SIGTERM handler that only sets a flag (async-signal-safe) -
* the actual i_quit() cleanup (unmapping the framebuffer, closing fds)
* runs later from i_poll_quit_signal(), called once per tic from a known
* safe point in the main loop rather than from the signal handler itself,
* so a supervisor process (nxstore) requesting an exit can never land in
* the middle of a frame's worth of direct framebuffer/heap access.
*/
void i_install_quit_signal(void);
/* Checks the flag set by the SIGTERM handler and calls i_quit() if it's
* set. Must only be called from a safe point in the main loop - see
* i_install_quit_signal().
*/
void i_poll_quit_signal(void);
void i_error(const char *error, ...) NORETURN PRINTF_ATTR(1, 2);
void i_tactile(int on, int off, int total);

View file

@ -92,6 +92,13 @@ struct graphics_state_s
uint8_t scale;
/* Pixel offset to center the scaled game viewport within the frame
* buffer when the buffer is larger than SCREENWIDTH/HEIGHT * scale.
*/
int xoffset;
int yoffset;
bool inited; /* Track initialization */
};
@ -262,13 +269,25 @@ static void blit_screen(void)
uint8_t p_idx;
void *fbptr;
/* TODO: It would be best to do this more efficiently/with less memory.
* It also would be good if we could handle the palette translation here
* such that DOOM can be played on frame buffers with differing bit depths
* and pixel formats.
*/
/* TODO: It would be best to do this more efficiently/with less memory. */
if (g_graphics_state.pinfo.bpp != 16 && g_graphics_state.pinfo.bpp != 32)
{
/* The xoffset/stride math below assumes one of those two pixel
* sizes, so continuing would read/write past the intended pixel
* bounds on the very first frame. Fail loudly instead of
* silently corrupting framebuffer memory.
*/
i_error("Unsupported framebuffer depth: %u bpp",
g_graphics_state.pinfo.bpp);
}
fbptr = g_graphics_state.fbmem +
g_graphics_state.yoffset * g_graphics_state.pinfo.stride +
g_graphics_state.xoffset *
(g_graphics_state.pinfo.bpp == 16 ? 2 : 4);
fbptr = g_graphics_state.fbmem;
for (unsigned y = 0; y < SCREENHEIGHT * g_graphics_state.scale; y++)
{
for (unsigned x = 0; x < SCREENWIDTH * g_graphics_state.scale; x++)
@ -277,9 +296,18 @@ static void blit_screen(void)
.scrnbuf[(y / g_graphics_state.scale) * SCREENWIDTH +
(x / g_graphics_state.scale)];
((uint32_t *)(fbptr))[x] =
ARGBTO32(g_palette[p_idx].a, g_palette[p_idx].r,
g_palette[p_idx].g, g_palette[p_idx].b);
if (g_graphics_state.pinfo.bpp == 16)
{
((uint16_t *)(fbptr))[x] =
RGBTO16(g_palette[p_idx].r, g_palette[p_idx].g,
g_palette[p_idx].b);
}
else
{
((uint32_t *)(fbptr))[x] =
ARGBTO32(g_palette[p_idx].a, g_palette[p_idx].r,
g_palette[p_idx].g, g_palette[p_idx].b);
}
}
fbptr += g_graphics_state.pinfo.stride;
@ -724,6 +752,18 @@ void i_init_graphics(void)
yscale = g_graphics_state.vinfo.yres / SCREENHEIGHT;
g_graphics_state.scale = xscale > yscale ? yscale : xscale;
/* Center the scaled viewport within the frame buffer rather than
* pinning it to the top-left corner, since the buffer is typically
* larger than SCREENWIDTH/HEIGHT * scale.
*/
g_graphics_state.xoffset =
(g_graphics_state.vinfo.xres -
SCREENWIDTH * g_graphics_state.scale) / 2;
g_graphics_state.yoffset =
(g_graphics_state.vinfo.yres -
SCREENHEIGHT * g_graphics_state.scale) / 2;
/* Get frame buffer plane info */
if (ioctl(g_graphics_state.fd, FBIOGET_PLANEINFO,
@ -732,6 +772,22 @@ void i_init_graphics(void)
i_error("ioctl(FBIOGET_PLANEINFO) failed: %d\n", errno);
}
/* Depth alone is not enough: several incompatible pixel layouts use 16
* or 32 bits. The conversion in blit_screen() emits RGB565 or RGB32.
*/
if ((g_graphics_state.pinfo.bpp == 16 &&
g_graphics_state.vinfo.fmt != FB_FMT_RGB16_565) ||
(g_graphics_state.pinfo.bpp == 32 &&
g_graphics_state.vinfo.fmt != FB_FMT_RGB32) ||
(g_graphics_state.pinfo.bpp != 16 &&
g_graphics_state.pinfo.bpp != 32))
{
i_error("Unsupported framebuffer format: fmt=%u, bpp=%u",
g_graphics_state.vinfo.fmt,
g_graphics_state.pinfo.bpp);
}
/* Initialize frame buffer memory for actual rendering */
g_graphics_state.fbmem =

View file

@ -1982,16 +1982,14 @@ static void save_default_collection(default_collection_t *collection)
*
****************************************************************************/
static int parse_int_parameter(const char *strparm)
static int parse_int_parameter(const char *strparm, int *param)
{
int param;
if (strparm[0] == '0' && strparm[1] == 'x')
sscanf(strparm + 2, "%x", (unsigned int *)&param);
else
sscanf(strparm, "%i", &param);
{
return sscanf(strparm + 2, "%x", (unsigned int *)param) == 1;
}
return param;
return sscanf(strparm, "%i", param) == 1;
}
static void set_variable(default_t *def, const char *value)
@ -2008,7 +2006,11 @@ static void set_variable(default_t *def, const char *value)
case DEFAULT_INT:
case DEFAULT_INT_HEX:
*def->location.i = parse_int_parameter(value);
if (parse_int_parameter(value, &intparm))
{
*def->location.i = intparm;
}
break;
case DEFAULT_KEY:
@ -2017,7 +2019,11 @@ static void set_variable(default_t *def, const char *value)
* file (save the old value in untranslated)
*/
intparm = parse_int_parameter(value);
if (!parse_int_parameter(value, &intparm))
{
break;
}
def->untranslated = intparm;
if (intparm >= 0 && intparm < 128)
{
@ -2082,6 +2088,7 @@ static void load_default_collection(default_collection_t *collection)
default_t *def;
char defname[80];
char strparm[100];
char line[256];
/* read the file in, overriding any set defaults */
@ -2096,12 +2103,28 @@ static void load_default_collection(default_collection_t *collection)
return;
}
while (!feof(f))
while (fgets(line, sizeof(line), f) != NULL)
{
if (fscanf(f, "%79s %99[^\n]\n", defname, strparm) != 2)
{
/* This line doesn't match */
strparm[0] = '\0';
/* Parse one physical line at a time. fscanf() with whitespace in
* its format can consume the next line as a missing value.
*/
if (strchr(line, '\n') == NULL &&
strlen(line) == sizeof(line) - 1)
{
int ch;
while ((ch = fgetc(f)) != '\n' && ch != EOF)
{
}
continue;
}
if (sscanf(line, "%79s %99[^\n]", defname, strparm) != 2)
{
continue;
}
@ -2136,6 +2159,11 @@ static void load_default_collection(default_collection_t *collection)
memmove(strparm, strparm + 1, sizeof(strparm) - 1);
}
if (strparm[0] == '\0')
{
continue;
}
set_variable(def, strparm);
}