diff --git a/manual/Makefile b/manual/Makefile
index a6c05db540..0790b87f49 100644
--- a/manual/Makefile
+++ b/manual/Makefile
@@ -69,6 +69,11 @@ chapters.% top-menu.%: libc-texinfo.sh $(texis-path) Makefile
 				'$(chapters)' \
 			       '$(appendices) $(licenses)'
 
+# Verify validity of texinfo sources against project rules.
+tests-special += \
+  $(objpfx)check-deftype.out \
+  $(objpfx)check-safety.out \
+  # tests-special
 
 $(objpfx)libc.dvi $(objpfx)libc.pdf $(objpfx)libc.info: \
 	$(addprefix $(objpfx),$(libc-texi-generated))
@@ -83,10 +88,19 @@ $(objpfx)summary.texi: $(objpfx)stamp-summary ;
 $(objpfx)stamp-summary: summary.pl $(filter-out $(objpfx)summary.texi, \
 					$(texis-path))
 	$(SHELL) ./check-safety.sh $(filter-out $(objpfx)%, $(texis-path))
+	$(SHELL) ./check-deftype.sh $(filter-out $(objpfx)%, $(texis-path))
 	LC_ALL=C $(PERL) $^ > $(objpfx)summary-tmp
 	$(move-if-change) $(objpfx)summary-tmp $(objpfx)summary.texi
 	touch $@
 
+$(objpfx)check-safety.out: check-safety.sh
+	$(SHELL) $< > $@ ; \
+	$(evaluate-test)
+
+$(objpfx)check-deftype.out: check-deftype.sh
+	$(SHELL) $< > $@ ; \
+	$(evaluate-test)
+
 # Generate a file which can be added to the `dir' content to provide direct
 # access to the documentation of the function, variables, and other
 # definitions.
@@ -153,7 +167,8 @@ $(objpfx)%.pdf: %.texinfo
 
 # Distribution.
 minimal-dist = summary.pl texis.awk tsort.awk libc-texinfo.sh libc.texinfo \
-	       libm-err.texi stamp-libm-err check-safety.sh		    \
+	       libm-err.texi stamp-libm-err check-safety.sh \
+	       check-deftype.sh \
 	       $(filter-out summary.texi, $(nonexamples))		    \
 	       $(patsubst %.c.texi,examples/%.c, $(examples))
 
diff --git a/manual/check-deftype.sh b/manual/check-deftype.sh
new file mode 100644
index 0000000000..395c99af6a
--- /dev/null
+++ b/manual/check-deftype.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+# Copyright 2024 Free Software Foundation, Inc.
+# This file is part of the GNU C Library.
+
+# The GNU C Library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+
+# The GNU C Library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+
+# You should have received a copy of the GNU Lesser General Public
+# License along with the GNU C Library; if not, see
+# <https://www.gnu.org/licenses/>.
+
+# Check that the @deftypefun command is called with the expected
+# arguments and includes checking for common mistakes including
+# failure to include a space after the function name, or incorrect
+# quoting.
+
+success=:
+
+# If no arguments are given, take all *.texi files in the current directory.
+test $# != 0 || set *.texi
+
+# We search for all @deftypefun and @deftypefunx command uses.
+# Then we remove all of those that match our expectations.
+# A @deftypefun or @deftypefunx command takes 3 arguments:
+# - return type
+# - name
+# - arguments
+# This is different from @deftypefn which includes an additional
+# category which is implicit here.
+grep -n -r '^@deftypefun' "$@" |
+grep -v '^.*@deftypefunx\?'\
+' \({\?[a-zA-Z0-9_ *]*}\?\) \([a-zA-Z0-9_]*\) (.*)$' &&
+success=false
+
+# We search for all @deftypefn and @deftypefnx command uses.
+# We have 4 arguments in the command including the category.
+grep -n -r '^@deftypefn' "$@" |
+grep -v '^.*@deftypefnx\?'\
+' {\?[a-zA-Z ]*}\? \({\?[a-zA-Z0-9@{}_ *]*}\?\) \([a-zA-Z0-9_]*\) (.*)$' &&
+success=false
+
+$success
diff --git a/manual/ipc.texi b/manual/ipc.texi
index 6a6e5ad410..32c5ac066f 100644
--- a/manual/ipc.texi
+++ b/manual/ipc.texi
@@ -20,7 +20,7 @@ by @theglibc{}.
 @c Need descriptions for all of these functions.
 
 @subsection System V Semaphores
-@deftypefun int semctl (int @var{semid}, int @var{semnum}, int @var{cmd});
+@deftypefun int semctl (int @var{semid}, int @var{semnum}, int @var{cmd})
 @safety{@prelim{}@mtsafe{}@assafe{}@acunsafe{@acucorrupt{/linux}}}
 @c syscall(ipc) ok
 @c
@@ -30,35 +30,35 @@ by @theglibc{}.
 @c semid_ds.
 @end deftypefun
 
-@deftypefun int semget (key_t @var{key}, int @var{nsems}, int @var{semflg});
+@deftypefun int semget (key_t @var{key}, int @var{nsems}, int @var{semflg})
 @safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
 @c syscall(ipc) ok
 @end deftypefun
 
-@deftypefun int semop (int @var{semid}, struct sembuf *@var{sops}, size_t @var{nsops});
+@deftypefun int semop (int @var{semid}, struct sembuf *@var{sops}, size_t @var{nsops})
 @safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
 @c syscall(ipc) ok
 @end deftypefun
 
-@deftypefun int semtimedop (int @var{semid}, struct sembuf *@var{sops}, size_t @var{nsops}, const struct timespec *@var{timeout});
+@deftypefun int semtimedop (int @var{semid}, struct sembuf *@var{sops}, size_t @var{nsops}, const struct timespec *@var{timeout})
 @safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
 @c syscall(ipc) ok
 @end deftypefun
 
 @subsection POSIX Semaphores
 
-@deftypefun int sem_init (sem_t *@var{sem}, int @var{pshared}, unsigned int @var{value});
+@deftypefun int sem_init (sem_t *@var{sem}, int @var{pshared}, unsigned int @var{value})
 @safety{@prelim{}@mtsafe{}@assafe{}@acunsafe{@acucorrupt{}}}
 @c Does not atomically update sem_t therefore AC-unsafe
 @c because it can leave sem_t partially initialized.
 @end deftypefun
 
-@deftypefun int sem_destroy (sem_t *@var{sem});
+@deftypefun int sem_destroy (sem_t *@var{sem})
 @safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
 @c Function does nothing and is therefore always safe.
 @end deftypefun
 
-@deftypefun sem_t *sem_open (const char *@var{name}, int @var{oflag}, ...);
+@deftypefun {sem_t *} sem_open (const char *@var{name}, int @var{oflag}, ...)
 @safety{@prelim{}@mtsafe{}@asunsafe{@asuinit{}}@acunsafe{@acuinit{}}}
 @c pthread_once asuinit
 @c
@@ -67,7 +67,7 @@ by @theglibc{}.
 @c shmfs on Linux.
 @end deftypefun
 
-@deftypefun int sem_close (sem_t *@var{sem});
+@deftypefun int sem_close (sem_t *@var{sem})
 @safety{@prelim{}@mtsafe{}@asunsafe{@asulock{}}@acunsafe{@aculock{}}}
 @c lll_lock asulock aculock
 @c twalk mtsrace{:root}
@@ -77,13 +77,13 @@ by @theglibc{}.
 @c are not updated atomically.
 @end deftypefun
 
-@deftypefun int sem_unlink (const char *@var{name});
+@deftypefun int sem_unlink (const char *@var{name})
 @safety{@prelim{}@mtsafe{}@asunsafe{@asuinit{}}@acunsafe{@acucorrupt{}}}
 @c pthread_once asuinit acucorrupt aculock
 @c mempcpy acucorrupt
 @end deftypefun
 
-@deftypefun int sem_wait (sem_t *@var{sem});
+@deftypefun int sem_wait (sem_t *@var{sem})
 @safety{@prelim{}@mtsafe{}@assafe{}@acunsafe{@acucorrupt{}}}
 @c atomic_fetch_add_relaxed (nwaiters) acucorrupt
 @c
@@ -95,22 +95,22 @@ by @theglibc{}.
 @c waiters count.
 @end deftypefun
 
-@deftypefun int sem_timedwait (sem_t *@var{sem}, const struct timespec *@var{abstime});
+@deftypefun int sem_timedwait (sem_t *@var{sem}, const struct timespec *@var{abstime})
 @safety{@prelim{}@mtsafe{}@assafe{}@acunsafe{@acucorrupt{}}}
 @c Same safety issues as sem_wait.
 @end deftypefun
 
-@deftypefun int sem_trywait (sem_t *@var{sem});
+@deftypefun int sem_trywait (sem_t *@var{sem})
 @safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
 @c All atomic operations are safe in all contexts.
 @end deftypefun
 
-@deftypefun int sem_post (sem_t *@var{sem});
+@deftypefun int sem_post (sem_t *@var{sem})
 @safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
 @c Same safety as sem_trywait.
 @end deftypefun
 
-@deftypefun int sem_getvalue (sem_t *@var{sem}, int *@var{sval});
+@deftypefun int sem_getvalue (sem_t *@var{sem}, int *@var{sval})
 @safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
 @c Atomic write of a value is safe in all contexts.
 @end deftypefun
diff --git a/manual/llio.texi b/manual/llio.texi
index a035c3e20f..8aa1f0622f 100644
--- a/manual/llio.texi
+++ b/manual/llio.texi
@@ -4831,12 +4831,12 @@ of an IOCTL, see @ref{Out-of-Band Data}.
 @manpagefunctionstub{poll,2}
 @end deftypefun
 
-@deftypefun int epoll_create(int @var{size})
+@deftypefun int epoll_create (int @var{size})
 
 @manpagefunctionstub{epoll_create,2}
 @end deftypefun
 
-@deftypefun int epoll_wait(int @var{epfd}, struct epoll_event *@var{events}, int @var{maxevents}, int @var{timeout})
+@deftypefun int epoll_wait (int @var{epfd}, struct epoll_event *@var{events}, int @var{maxevents}, int @var{timeout})
 
 @manpagefunctionstub{epoll_wait,2}
 @end deftypefun
diff --git a/manual/memory.texi b/manual/memory.texi
index 3710d7ec66..58683ee93d 100644
--- a/manual/memory.texi
+++ b/manual/memory.texi
@@ -2935,7 +2935,7 @@ exceed the process' data storage limit.
 @end deftypefun
 
 
-@deftypefun void *sbrk (ptrdiff_t @var{delta})
+@deftypefun {void *} sbrk (ptrdiff_t @var{delta})
 @standards{BSD, unistd.h}
 @safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
 
diff --git a/manual/stdio.texi b/manual/stdio.texi
index c7a2b4a9a1..75aee8ab08 100644
--- a/manual/stdio.texi
+++ b/manual/stdio.texi
@@ -2531,7 +2531,7 @@ store the result in which case @code{-1} is returned.  This was
 changed in order to comply with the @w{ISO C99} standard.
 @end deftypefun
 
-@deftypefun dprintf (int @var{fd}, @var{template}, ...)
+@deftypefun int dprintf (int @var{fd}, @var{template}, ...)
 @standards{POSIX, stdio.h}
 @safety{@mtsafe{@mtslocale{}}@asunsafe{@ascuheap{}}@acunsafe{@acsmem{}}}
 This function formats its arguments according to @var{template} and
diff --git a/manual/threads.texi b/manual/threads.texi
index 25e99c9606..9ea137cb96 100644
--- a/manual/threads.texi
+++ b/manual/threads.texi
@@ -592,7 +592,7 @@ destructor for the thread-specific data is not called during destruction, nor
 is it called during thread exit.
 @end deftypefun
 
-@deftypefun void *pthread_getspecific (pthread_key_t @var{key})
+@deftypefun {void *} pthread_getspecific (pthread_key_t @var{key})
 @standards{POSIX, pthread.h}
 @safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
 @c pthread_getspecific ok
diff --git a/manual/time.texi b/manual/time.texi
index 64aad8fdc5..90bc9a2566 100644
--- a/manual/time.texi
+++ b/manual/time.texi
@@ -1829,7 +1829,7 @@ can be placed in the buffer @var{s} the return value is zero, with the
 same problems indicated in the @code{strftime} documentation.
 @end deftypefun
 
-@deftypefun {Deprecated function} {char *} asctime (const struct tm *@var{brokentime})
+@deftypefn {Deprecated function} {char *} asctime (const struct tm *@var{brokentime})
 @standards{ISO, time.h}
 @safety{@prelim{}@mtunsafe{@mtasurace{:asctime} @mtslocale{}}@asunsafe{}@acsafe{}}
 @c asctime @mtasurace:asctime @mtslocale
@@ -1863,9 +1863,9 @@ string.)
 @strong{Portability note:}
 This obsolescent function is deprecated in C23.
 Programs should instead use @code{strftime} or even @code{sprintf}.
-@end deftypefun
+@end deftypefn
 
-@deftypefun {Deprecated function} {char *} asctime_r (const struct tm *@var{brokentime}, char *@var{buffer})
+@deftypefn {Deprecated function} {char *} asctime_r (const struct tm *@var{brokentime}, char *@var{buffer})
 @standards{???, time.h}
 @safety{@prelim{}@mtsafe{@mtslocale{}}@assafe{}@acsafe{}}
 @c asctime_r @mtslocale
@@ -1884,9 +1884,9 @@ it returns @code{NULL}.
 @strong{Portability Note:}
 POSIX.1-2024 removed this obsolescent function.
 Programs should instead use @code{strftime} or even @code{sprintf}.
-@end deftypefun
+@end deftypefn
 
-@deftypefun {Deprecated function} {char *} ctime (const time_t *@var{time})
+@deftypefn {Deprecated function} {char *} ctime (const time_t *@var{time})
 @standards{ISO, time.h}
 @safety{@prelim{}@mtunsafe{@mtasurace{:tmbuf} @mtasurace{:asctime} @mtsenv{} @mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
 @c ctime @mtasurace:tmbuf @mtasurace:asctime @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@@ -1909,9 +1909,9 @@ Calling @code{ctime} also sets the time zone state as if
 @strong{Portability note:}
 This obsolescent function is deprecated in C23.
 Programs should instead use @code{strftime} or even @code{sprintf}.
-@end deftypefun
+@end deftypefn
 
-@deftypefun {Deprecated function} {char *} ctime_r (const time_t *@var{time}, char *@var{buffer})
+@deftypefn {Deprecated function} {char *} ctime_r (const time_t *@var{time}, char *@var{buffer})
 @standards{???, time.h}
 @safety{@prelim{}@mtsafe{@mtsenv{} @mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
 @c ctime_r @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@@ -1935,7 +1935,7 @@ it returns @code{NULL}.
 @strong{Portability Note:}
 POSIX.1-2024 removed this obsolescent function.
 Programs should instead use @code{strftime} or even @code{sprintf}.
-@end deftypefun
+@end deftypefn
 
 @node Parsing Date and Time
 @subsection Convert textual time and date information back
