Commit 021622df authored by Stephen Kitt's avatar Stephen Kitt Committed by Jonathan Corbet
Browse files

docs: add a script to check sysctl docs



This script allows sysctl documentation to be checked against the
kernel source code, to identify missing or obsolete entries. Running
it against 5.5 shows for example that sysctl/kernel.rst has two
obsolete entries and is missing 52 entries.

Signed-off-by: default avatarStephen Kitt <steve@sk2.org>
Signed-off-by: default avatarJonathan Corbet <corbet@lwn.net>
parent bf347b9d
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -2,6 +2,9 @@
Documentation for /proc/sys/kernel/
===================================

.. See scripts/check-sysctl-docs to keep this up to date


Copyright (c) 1998, 1999,  Rik van Riel <riel@nl.linux.org>

Copyright (c) 2009,        Shen Feng<shen@cn.fujitsu.com>
+181 −0
Original line number Diff line number Diff line
#!/usr/bin/gawk -f
# SPDX-License-Identifier: GPL-2.0

# Script to check sysctl documentation against source files
#
# Copyright (c) 2020 Stephen Kitt

# Example invocation:
#	scripts/check-sysctl-docs -vtable="kernel" \
#		Documentation/admin-guide/sysctl/kernel.rst \
#		$(git grep -l register_sysctl_)
#
# Specify -vdebug=1 to see debugging information

BEGIN {
    if (!table) {
	print "Please specify the table to look for using the table variable" > "/dev/stderr"
	exit 1
    }
}

# The following globals are used:
# children: maps ctl_table names and procnames to child ctl_table names
# documented: maps documented entries (each key is an entry)
# entries: maps ctl_table names and procnames to counts (so
#          enumerating the subkeys for a given ctl_table lists its
#          procnames)
# files: maps procnames to source file names
# paths: maps ctl_path names to paths
# curpath: the name of the current ctl_path struct
# curtable: the name of the current ctl_table struct
# curentry: the name of the current proc entry (procname when parsing
#           a ctl_table, constructed path when parsing a ctl_path)


# Remove punctuation from the given value
function trimpunct(value) {
    while (value ~ /^["&]/) {
	value = substr(value, 2)
    }
    while (value ~ /[]["&,}]$/) {
	value = substr(value, 1, length(value) - 1)
    }
    return value
}

# Print the information for the given entry
function printentry(entry) {
    seen[entry]++
    printf "* %s from %s", entry, file[entry]
    if (documented[entry]) {
	printf " (documented)"
    }
    print ""
}


# Stage 1: build the list of documented entries
FNR == NR && /^=+$/ {
    if (prevline ~ /Documentation for/) {
	# This is the main title
	next
    }

    # The previous line is a section title, parse it
    $0 = prevline
    if (debug) print "Parsing " $0
    inbrackets = 0
    for (i = 1; i <= NF; i++) {
	if (length($i) == 0) {
	    continue
	}
	if (!inbrackets && substr($i, 1, 1) == "(") {
	    inbrackets = 1
	}
	if (!inbrackets) {
	    token = trimpunct($i)
	    if (length(token) > 0 && token != "and") {
		if (debug) print trimpunct($i)
		documented[trimpunct($i)]++
	    }
	}
	if (inbrackets && substr($i, length($i), 1) == ")") {
	    inbrackets = 0
	}
    }
}

FNR == NR {
    prevline = $0
    next
}


# Stage 2: process each file and find all sysctl tables
BEGINFILE {
    delete children
    delete entries
    delete paths
    curpath = ""
    curtable = ""
    curentry = ""
    if (debug) print "Processing file " FILENAME
}

/^static struct ctl_path/ {
    match($0, /static struct ctl_path ([^][]+)/, tables)
    curpath = tables[1]
    if (debug) print "Processing path " curpath
}

/^static struct ctl_table/ {
    match($0, /static struct ctl_table ([^][]+)/, tables)
    curtable = tables[1]
    if (debug) print "Processing table " curtable
}

/^};$/ {
    curpath = ""
    curtable = ""
    curentry = ""
}

curpath && /\.procname[\t ]*=[\t ]*".+"/ {
    match($0, /.procname[\t ]*=[\t ]*"([^"]+)"/, names)
    if (curentry) {
	curentry = curentry "/" names[1]
    } else {
	curentry = names[1]
    }
    if (debug) print "Setting path " curpath " to " curentry
    paths[curpath] = curentry
}

curtable && /\.procname[\t ]*=[\t ]*".+"/ {
    match($0, /.procname[\t ]*=[\t ]*"([^"]+)"/, names)
    curentry = names[1]
    if (debug) print "Adding entry " curentry " to table " curtable
    entries[curtable][curentry]++
    file[curentry] = FILENAME
}

/\.child[\t ]*=/ {
    child = trimpunct($NF)
    if (debug) print "Linking child " child " to table " curtable " entry " curentry
    children[curtable][curentry] = child
}

/register_sysctl_table\(.*\)/ {
    match($0, /register_sysctl_table\(([^)]+)\)/, tables)
    if (debug) print "Registering table " tables[1]
    if (children[tables[1]][table]) {
	for (entry in entries[children[tables[1]][table]]) {
	    printentry(entry)
	}
    }
}

/register_sysctl_paths\(.*\)/ {
    match($0, /register_sysctl_paths\(([^)]+), ([^)]+)\)/, tables)
    if (debug) print "Attaching table " tables[2] " to path " tables[1]
    if (paths[tables[1]] == table) {
	for (entry in entries[tables[2]]) {
	    printentry(entry)
	}
    }
    split(paths[tables[1]], components, "/")
    if (length(components) > 1 && components[1] == table) {
	# Count the first subdirectory as seen
	seen[components[2]]++
    }
}


END {
    for (entry in documented) {
	if (!seen[entry]) {
	    print "No implementation for " entry
	}
    }
}