# Usage: cd $PLAN9; awk -f dist/checkman.awk man?/*.?
#
# Checks:
#	- .TH is first line, and has proper name section number
#	- sections are in order NAME, SYNOPSIS, DESCRIPTION, EXAMPLES,
#		FILES, SOURCE, SEE ALSO, DIAGNOSTICS, BUGS
#	- there's a manual page for each cross-referenced page

BEGIN {

#	.SH sections should come in the following order

	Weight["NAME"] = 1
	Weight["SYNOPSIS"] = 2
	Weight["DESCRIPTION"] = 4
	Weight["EXAMPLE"] = 8
	Weight["EXAMPLES"] = 16
	Weight["FILES"] = 32
	Weight["SOURCE"] = 64
	Weight["SEE ALSO"] = 128
	Weight["DIAGNOSTICS"] = 256
	Weight["SYSTEM CALLS"] = 512
	Weight["BUGS"] = 1024

	Skipdirs["CVS"] = 1

	# allow references to pages provded
	# by the underlying Unix system
	Omitman["awk(1)"] = 1
	Omitman["bash(1)"] = 1
	Omitman["chmod(1)"] = 1
	Omitman["compress(1)"] = 1
	Omitman["cp(1)"] = 1
	Omitman["egrep(1)"] = 1
	Omitman["gs(1)"] = 1
	Omitman["gv(1)"] = 1
	Omitman["lex(1)"] = 1
	Omitman["lp(1)"] = 1
	Omitman["lpr(1)"] = 1
	Omitman["mail(1)"] = 1
	Omitman["make(1)"] = 1
	Omitman["nm(1)"] = 1
	Omitman["prof(1)"] = 1
	Omitman["pwd(1)"] = 1
	Omitman["qiv(1)"] = 1
	Omitman["sftp(1)"] = 1
	Omitman["sh(1)"] = 1
	Omitman["ssh(1)"] = 1
	Omitman["stty(1)"] = 1
	Omitman["tex(1)"] = 1
	Omitman["unutf(1)"] = 1
	Omitman["vnc(1)"] = 1
	Omitman["xterm(1)"] = 1

	Omitman["access(2)"] = 1
	Omitman["brk(2)"] = 1
	Omitman["chdir(2)"] = 1
	Omitman["close(2)"] = 1
	Omitman["connect(2)"] = 1
	Omitman["fork(2)"] = 1
	Omitman["gethostname(2)"] = 1
	Omitman["getpid(2)"] = 1
	Omitman["getuid(2)"] = 1
	Omitman["open(2)"] = 1
	Omitman["pipe(2)"] = 1
	Omitman["ptrace(2)"] = 1
	Omitman["rmdir(2)"] = 1
	Omitman["send(2)"] = 1
	Omitman["signal(2)"] = 1
	Omitman["sigprocmask(2)"] = 1
	Omitman["socketpair(2)"] = 1
	Omitman["unlink(2)"] = 1

	Omitman["abort(3)"] = 1
	Omitman["assert(3)"] = 1
	Omitman["fprintf(3)"] = 1
	Omitman["fscanf(3)"] = 1
	Omitman["fopen(3)"] = 1
	Omitman["isalpha(3)"] = 1
	Omitman["malloc(3)"] = 1
	Omitman["perror(3)"] = 1
	Omitman["remove(3)"] = 1
	Omitman["sin(3)"] = 1
	Omitman["strerror(3)"] = 1

	Omitman["core(5)"] = 1
	Omitman["passwd(5)"] = 1

	Omitman["signal(7)"] = 1

	Omitman["cron(8)"] = 1
	Omitman["mount(8)"] = 1

	# don't need documentation for these in bin
	Omitted[".cvsignore"] = 1
	Omitted["Getdir"] = 1
	Omitted["Irc"] = 1
	Omitted["Juke"] = 1
	Omitted["ajuke"] = 1
	Omitted["goodmk"] = 1
	Omitted["jukefmt"] = 1
	Omitted["jukeget"] = 1
	Omitted["jukeindex"] = 1
	Omitted["jukeinfo"] = 1
	Omitted["jukeplay"] = 1
	Omitted["jukeput"] = 1
	Omitted["jukesearch"] = 1
	Omitted["jukesongfile"] = 1
	Omitted["m4ainfo"] = 1
	Omitted["mp3info"] = 1
	Omitted["notes"] = 1
	Omitted["tcolors"] = 1
	Omitted["tref"] = 1
	Omitted["unutf"] = 1
	Omitted["volume"] = 1
	Omitted["vtdump"] = 1
	Omitted["netfilelib.rc"] = 1

	# not for users
	Omittedlib["creadimage"] = 1
	Omittedlib["pixelbits"] = 1
	Omittedlib["bouncemouse"] = 1
	Omittedlib["main"] = 1	# in libthread
	
	Omittedlib["opasstokey"] = 1	# in libauthsrv

	# functions provided for -lthread_db
	Omittedlib["ps_get_thread_area"] = 1
	Omittedlib["ps_getpid"] = 1
	Omittedlib["ps_lcontinue"] = 1
	Omittedlib["ps_lgetfpregs"] = 1
	Omittedlib["ps_lgetregs"] = 1
	Omittedlib["ps_lsetfpregs"] = 1
	Omittedlib["ps_lsetregs"] = 1
	Omittedlib["ps_lstop"] = 1
	Omittedlib["ps_pcontinue"] = 1
	Omittedlib["ps_pdread"] = 1
	Omittedlib["ps_pdwrite"] = 1
	Omittedlib["ps_pglobal_lookup"] = 1
	Omittedlib["ps_pstop"] = 1
	Omittedlib["ps_ptread"] = 1
	Omittedlib["ps_ptwrite"] = 1

	# libmach includes a small dwarf and elf library
	Omittedlib["corecmdfreebsd386"] = 1
	Omittedlib["corecmdlinux386"] = 1
	Omittedlib["coreregsfreebsd386"] = 1
	Omittedlib["coreregslinux386"] = 1
	Omittedlib["coreregsmachopower"] = 1
	Omittedlib["crackelf"] = 1
	Omittedlib["crackmacho"] = 1
	Omittedlib["dwarfaddrtounit"] = 1
	Omittedlib["dwarfclose"] = 1
	Omittedlib["dwarfenum"] = 1
	Omittedlib["dwarfenumunit"] = 1
	Omittedlib["dwarfget1"] = 1
	Omittedlib["dwarfget128"] = 1
	Omittedlib["dwarfget128s"] = 1
	Omittedlib["dwarfget2"] = 1
	Omittedlib["dwarfget4"] = 1
	Omittedlib["dwarfget8"] = 1
	Omittedlib["dwarfgetabbrev"] = 1
	Omittedlib["dwarfgetaddr"] = 1
	Omittedlib["dwarfgetn"] = 1
	Omittedlib["dwarfgetnref"] = 1
	Omittedlib["dwarfgetstring"] = 1
	Omittedlib["dwarflookupfn"] = 1
	Omittedlib["dwarflookupname"] = 1
	Omittedlib["dwarflookupnameinunit"] = 1
	Omittedlib["dwarflookupsubname"] = 1
	Omittedlib["dwarflookuptag"] = 1
	Omittedlib["dwarfnextsym"] = 1
	Omittedlib["dwarfnextsymat"] = 1
	Omittedlib["dwarfopen"] = 1
	Omittedlib["dwarfpctoline"] = 1
	Omittedlib["dwarfseeksym"] = 1
	Omittedlib["dwarfskip"] = 1
	Omittedlib["dwarfunwind"] = 1
	Omittedlib["elfclose"] = 1
	Omittedlib["elfdl386mapdl"] = 1
	Omittedlib["elfinit"] = 1
	Omittedlib["elfmachine"] = 1
	Omittedlib["elfmap"] = 1
	Omittedlib["elfopen"] = 1
	Omittedlib["elfsection"] = 1
	Omittedlib["elfsym"] = 1
	Omittedlib["elfsymlookup"] = 1
	Omittedlib["elftype"] = 1
	Omittedlib["machoclose"] = 1
	Omittedlib["machoinit"] = 1
	Omittedlib["machoopen"] = 1
	Omittedlib["stabsym"] = 1
	Omittedlib["symdwarf"] = 1
	Omittedlib["symelf"] = 1
	Omittedlib["symmacho"] = 1
	Omittedlib["symstabs"] = 1
	Omittedlib["elfcorelinux386"] = 1
	Omittedlib["linux2ureg386"] = 1
	Omittedlib["ureg2linux386"] = 1
	Omittedlib["coreregs"] = 1	# haven't documented mach yet
	Omittedlib["regdesc"] = 1

	Omittedlib["auth_attr"] = 1	# not happy about this

	Omittedlib["ndbnew"] = 1		# private to library
	Omittedlib["ndbsetval"] = 1

	Renamelib["chanalt"] = "alt"
	Renamelib["channbrecv"] = "nbrecv"
	Renamelib["channbrecvp"] = "nbrecvp"
	Renamelib["channbrecvul"] = "nbrecvul"
	Renamelib["channbsend"] = "nbsend"
	Renamelib["channbsendp"] = "nbsendp"
	Renamelib["channbsendul"] = "nbsendul"
	Renamelib["chanrecv"] = "recv"
	Renamelib["chanrecvp"] = "recvp"
	Renamelib["chanrecvul"] = "recvul"
	Renamelib["chansend"] = "send"
	Renamelib["chansendp"] = "sendp"
	Renamelib["chansendul"] = "sendul"
	Renamelib["threadyield"] = "yield"

	Renamelib["fmtcharstod"] = "charstod"
	Renamelib["fmtstrtod"] = "strtod"

	Renamelib["regcomp9"] = "regcomp"
	Renamelib["regcomplit9"] = "regcomplit"
	Renamelib["regcompnl9"] = "regcompnl"
	Renamelib["regerror9"] = "regerror"
	Renamelib["regexec9"] = "regexec"
	Renamelib["regsub9"] = "regsub"
	Renamelib["rregexec9"] = "rregexec"
	Renamelib["rregsub9"] = "rregsub"

	lastline = "XXX";
	lastfile = FILENAME;
}

func getnmlist(lib,    cmd)
{
	cmd = "nm -g " lib
	while (cmd | getline) {
		if (($2 == "T" || $2 == "L") && $3 !~ "^_"){
			sym = $3
			sub("^p9", "", sym)
			if(sym in Renamelib)
				List[Renamelib[sym]] = lib " as " sym
			else
				List[sym] = lib
		}
	}
	close(cmd)
}


func getindex(dir,    fname)
{
	fname = dir "/INDEX"
	while ((getline < fname) > 0)
		Index[$1] = dir
	close(fname)
}

func getbinlist(dir,    cmd, subdirs, nsd)
{
	cmd = "ls -p -l " dir
	nsd = 0
	while (cmd | getline) {
		if ($1 ~ /^d/) {
			if (!($10 in Skipdirs))
				subdirs[++nsd] = $10
		} else if ($10 !~ "^_")
			List[$10] = dir
	}
	for ( ; nsd > 0 ; nsd--)
		getbinlist(dir "/" subdirs[nsd])
	close(cmd)
}

func clearindex(    i)
{
	for (i in Index)
		delete Index[i]
}

func clearlist(    i)
{
	for (i in List)
		delete List[i]
}


FNR==1	{
	if(lastline == ""){
		# screws up troff headers
		print lastfile ":$ is a blank line"
	}

	n = length(FILENAME)
	nam = FILENAME
	if(nam ~ /\.html$/)
		next
	if(nam !~ /^man\/man(.*)\/(.*)\.(.*)$/){
		print "nam", nam, "not of form [0-9][0-9]?/*"
		next
	}
	nam = substr(nam, 8)
	gsub("[/.]", " ", nam);
	n = split(nam, a)
	sec = a[1]
	name = a[2]
	section = a[3]
	if($1 != ".TH" || NF != 3)
		print "First line of", FILENAME, "not a proper .TH"
	else if(($2 != toupper(name) || substr($3, 1, length(sec)) != sec || $3 != toupper(section)) \
			&& ($2!="INTRO" || name!="0intro") \
			&& (name !~ /^9/ || $2!=toupper(substr(name, 2)))){
		print ".TH of", FILENAME, "doesn't match filename"
	}else
		Pages[tolower($2) "(" tolower($3) ")"] = 1
	Sh = 0
}

{ lastline=$0; lastfile=FILENAME; }

$1 == ".SH" {
	if(inex)
		print "Unterminated .EX in", FILENAME, ":", $0
	inex = 0;
	if (substr($2, 1, 1) == "\"") {
		if (NF == 2) {
			print "Unneeded quote in", FILENAME, ":", $0
			$2 = substr($2, 2, length($2)-2)
		} else if (NF == 3) {
			$2 = substr($2, 2) substr($3, 1, length($3)-1)
			NF = 2
		}
	}
	if(Sh == 0 && $2 != "NAME")
		print FILENAME, "has no .SH NAME"
	w = Weight[$2]
	if (w) {
		if (w < Sh)
			print "Heading", $2, "out of order in", FILENAME
		Sh += w
	}
	sh = $2
}

$1 == ".EX" {
		if(inex)
			print "Nested .EX in", FILENAME ":" FNR, ":", $0
		inex = 1
}

$1 == ".EE" {
	if(!inex)
		print "Bad .EE in", FILENAME ":" FNR ":", $0
	inex = 0;
}

$1 == ".TF" {
	smallspace = 1
}

$1 == ".PD" || $1 == ".SH" || $1 == ".SS" || $1 == ".TH" {
	smallspace = 0
}

$1 == ".RE" {
	lastre = 1
}

$1 == ".PP" {
	if(smallspace && !lastre)
		print "Possible missing .PD at " FILENAME ":" FNR
	smallspace = 0
}

$1 != ".RE" {
	lastre = 0
}

sh == "BUGS" && $1 == ".br" {
	print FILENAME ":" FNR ": .br in BUGS"
}

sh == "SOURCE" && $1 ~ /^\\\*9\// {
	s = ENVIRON["PLAN9"] substr($1, 4)
	Sources[s] = 1
}

sh == "SOURCE" && $2 ~ /^\\\*9\// {
	s = ENVIRON["PLAN9"] substr($2, 4)
	Sources[s] = 1
}

sh == "SOURCE" && $1 ~ /^\// {
	Sources[$1] = 1
}

sh == "SOURCE" && $2 ~ /^\// {
	Sources[$2] = 1
}

$0 ~ /^\.[A-Z].*\([1-9]\)/ {
		if ($1 == ".IR" && $3 ~ /\([0-9]\)/) {
			name = $2
			section = $3
		}else if ($1 == ".RI" && $2 == "(" && $4 ~ /\([0-9]\)/) {
			name = $3
			section = $4
		}else if ($1 == ".IR" && $3 ~ /9.\([0-9]\)/) {
			name = $2
			section = "9"
		}else if ($1 == ".RI" && $2 == "(" && $4 ~ /9.\([0-9]\)/) {
			name = $3
			section = "9"
		} else {
			if ($1 == ".HR" && $3 == "\"Section")
				next;
			print "Possible bad cross-reference format in", FILENAME ":" FNR
			print $0
			next
		}
		gsub(/[^0-9]/, "", section)
		Refs[toupper(name) "(" section ")"]++
}

END {
	if(lastline == ""){
		print lastfile ":$ is a blank line"
	}

	print "Checking Source References"
	cmd = "xargs -n 100 ls -d 2>&1 >/dev/null | sed 's/^ls: /	/; s/: .*//'"
	for (i in Sources) {
		print i |cmd
	}
	close(cmd)
	print ""
	print "Checking Cross-Referenced Pages"
	for (i in Refs) {
		if (!(tolower(i) in Pages) && !(tolower(i) in Omitman)){
			b = tolower(i)
			gsub("\\(", " \\(", b)
			gsub("\\)", "\\)", b)
			split(tolower(i), a, "/")
			print "egrep -in '^\\.IR.*" b "' $PLAN9/man/man*/* # Need " tolower(i) |"sort"
		}
	}
	close("sort")
	print ""
	print "Checking commands"
	getindex("man/man1")
	getindex("man/man4")
	getindex("man/man7")
	getindex("man/man8")
	getbinlist("bin")
	for (i in List) {
		if (!(i in Index) && !(i in Omitted))
			print "Need", i, "(in " List[i] ")" |"sort"
	}
	close("sort")
	print ""
	for (i in List) {
		if (!(i in Index) && (i in Omitted))
			print "Omit", i, "(in " List[i] ")" |"sort"
	}
	close("sort")
	clearindex()
	clearlist()
	print ""
	print "Checking libraries"
	getindex("man/man3")
	getnmlist("lib/lib9.a")
	getnmlist("lib/lib9p.a")
	getnmlist("lib/lib9pclient.a")
	getnmlist("lib/libString.a")
	getnmlist("lib/libauth.a")
	getnmlist("lib/libauthsrv.a")
	getnmlist("lib/libbin.a")
	getnmlist("lib/libbio.a")
	getnmlist("lib/libcomplete.a")
	# getnmlist("lib/libcontrol.a")
	getnmlist("lib/libdisk.a")
	getnmlist("lib/libdraw.a")
	getnmlist("lib/libflate.a")
	getnmlist("lib/libframe.a")
	getnmlist("lib/libgeometry.a")
	getnmlist("lib/libhtml.a")
	# getnmlist("lib/libhttpd.a")
	getnmlist("lib/libip.a")
	getnmlist("lib/libmach.a")
	# getnmlist("lib/libmemdraw.a")
	# getnmlist("lib/libmemlayer.a")
	getnmlist("lib/libmp.a")
	getnmlist("lib/libmux.a")
	getnmlist("lib/libndb.a")
	getnmlist("lib/libplumb.a")
	getnmlist("lib/libregexp9.a")
	getnmlist("lib/libsec.a")
	getnmlist("lib/libthread.a")
	# getnmlist("lib/libventi.a")
	for (i in List) {
		if (!(i in Index) && !(i in Omittedlib))
			print "Need", List[i], i |"sort"
			# print "Need", i, "(in " List[i] ")" |"sort"
	}
	close("sort")
	print ""
	for (i in List) {
		if (!(i in Index) && (i in Omittedlib))
			print "Omit", List[i], i |"sort"
			# print "Omit", i, "(in " List[i] ")" |"sort"
	}
	close("sort")
}

