Skip to content

Race condition in building clinetparse? #29

@mcepl

Description

@mcepl

Building of dictd failed in Fedora (https://bugzilla.redhat.com/2433984 and https://koji.fedoraproject.org/koji/taskinfo?taskID=141152566) with this error:

libtool --tag=CC --mode=compile gcc -c -DHAVE_CONFIG_H -O2 -flto=auto -ffat-lto-objects -fexceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-U_FORTIFY_SOURCE,-D_FORTIFY_SOURCE=3 -Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1  -m32 -march=i686 -mtune=generic -msse2 -mfpmath=sse -mstackrealign -fasynchronous-unwind-tables -fstack-clash-protection -mtls-dialect=gnu   -Wall -Wwrite-strings -Wcast-align -Wmissing-prototypes -Wshadow -Wnested-externs -Waggregate-return -DDICT_PLUGIN_PATH=\"/usr/libexec/\" -DDICT_DICTIONARY_PATH=\"/usr/share/\" -DDICT_VERSION=\"1.13.3\" -DDICT_CONFIG_PATH=\"/etc/\" -I. -I. dictd.c -o dictd.o
y.tab.c:132: error: unterminated #ifndef
servparse.y:34:12: warning: 'string2bool' defined but not used [-Wunused-function]
   34 | static int string2bool (const char *str)
      |            ^~~~~~~~~~~
servparse.y:32:22: warning: 'db' defined but not used [-Wunused-variable]
   32 | static dictDatabase *db;
      |                      ^~
make: *** [Makefile:120: clientparse.o] Error 1
make: *** Waiting for unfinished jobs....
mv: cannot stat 'y.tab.h': No such file or directory
mv: cannot stat 'y.tab.c'

This looks like race condition in this Makefile rule:

clientparse.c clientparse.h: clientparse.y
    $(YACC) -tdv $<; \
    cmp -s y.tab.h clientparse.h || mv y.tab.h clientparse.h; \
    cmp -s y.tab.c clientparse.c || mv y.tab.c clientparse.c; \
    rm -f y.tab.h y.tab.c

In a parallel build, make sees that both clientparse.c and clientparse.h are missing. Because they are listed on the same line but generated by a command that produces fixed-name temporary files (y.tab.c/h), the following happens:

  1. Multiple Processes: make may launch two separate sub-shells to satisfy the two targets simultaneously.

  2. File Overwriting: Both sub-shells run yacc at the same time. One shell might be running mv y.tab.c clientparse.c while the other shell's yacc process is still writing to y.tab.c.

  3. Truncated Files: The compiler (gcc) starts reading clientparse.c while it is in an incomplete state (half-moved or half-written), leading to the unterminated #ifndef error you saw.

If your yacc has -o option (e.g., GNU/Bison has it, and the OpenBSD one seems to have it as well, but it is not POSIX), then we could try something like:

# Replace the existing multi-line rules with these:
servparse.c servparse.h: servparse.y
	$(YACC) -d -o servparse.c $<

clientparse.c clientparse.h: clientparse.y
	$(YACC) -d -o clientparse.c $<

With POSIX yacc and POSIX make (i.e., without pattern rules of GNU/Make), we could slightly more complicated way:

# 1. Define that the header depends on the C file
clientparse.h: clientparse.c
	@if test ! -f $@; then \
		rm -f clientparse.c; \
		$(MAKE) clientparse.c; \
	fi

# 2. The actual rule that runs YACC
clientparse.c: clientparse.y
	$(YACC) -tdv $(srcdir)/clientparse.y
	mv y.tab.h clientparse.h
	mv y.tab.c clientparse.c
	rm -f y.tab.h y.tab.c

What do you think?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions