0001 #!/usr/bin/gawk -f
0002 # SPDX-License-Identifier: GPL-2.0
0003
0004 # Script to check sysctl documentation against source files
0005 #
0006 # Copyright (c) 2020 Stephen Kitt
0007
0008 # Example invocation:
0009 # scripts/check-sysctl-docs -vtable="kernel" \
0010 # Documentation/admin-guide/sysctl/kernel.rst \
0011 # $(git grep -l register_sysctl_)
0012 #
0013 # Specify -vdebug=1 to see debugging information
0014
0015 BEGIN {
0016 if (!table) {
0017 print "Please specify the table to look for using the table variable" > "/dev/stderr"
0018 exit 1
0019 }
0020 }
0021
0022 # The following globals are used:
0023 # children: maps ctl_table names and procnames to child ctl_table names
0024 # documented: maps documented entries (each key is an entry)
0025 # entries: maps ctl_table names and procnames to counts (so
0026 # enumerating the subkeys for a given ctl_table lists its
0027 # procnames)
0028 # files: maps procnames to source file names
0029 # paths: maps ctl_path names to paths
0030 # curpath: the name of the current ctl_path struct
0031 # curtable: the name of the current ctl_table struct
0032 # curentry: the name of the current proc entry (procname when parsing
0033 # a ctl_table, constructed path when parsing a ctl_path)
0034
0035
0036 # Remove punctuation from the given value
0037 function trimpunct(value) {
0038 while (value ~ /^["&]/) {
0039 value = substr(value, 2)
0040 }
0041 while (value ~ /[]["&,}]$/) {
0042 value = substr(value, 1, length(value) - 1)
0043 }
0044 return value
0045 }
0046
0047 # Print the information for the given entry
0048 function printentry(entry) {
0049 seen[entry]++
0050 printf "* %s from %s", entry, file[entry]
0051 if (documented[entry]) {
0052 printf " (documented)"
0053 }
0054 print ""
0055 }
0056
0057
0058 # Stage 1: build the list of documented entries
0059 FNR == NR && /^=+$/ {
0060 if (prevline ~ /Documentation for/) {
0061 # This is the main title
0062 next
0063 }
0064
0065 # The previous line is a section title, parse it
0066 $0 = prevline
0067 if (debug) print "Parsing " $0
0068 inbrackets = 0
0069 for (i = 1; i <= NF; i++) {
0070 if (length($i) == 0) {
0071 continue
0072 }
0073 if (!inbrackets && substr($i, 1, 1) == "(") {
0074 inbrackets = 1
0075 }
0076 if (!inbrackets) {
0077 token = trimpunct($i)
0078 if (length(token) > 0 && token != "and") {
0079 if (debug) print trimpunct($i)
0080 documented[trimpunct($i)]++
0081 }
0082 }
0083 if (inbrackets && substr($i, length($i), 1) == ")") {
0084 inbrackets = 0
0085 }
0086 }
0087 }
0088
0089 FNR == NR {
0090 prevline = $0
0091 next
0092 }
0093
0094
0095 # Stage 2: process each file and find all sysctl tables
0096 BEGINFILE {
0097 delete children
0098 delete entries
0099 delete paths
0100 curpath = ""
0101 curtable = ""
0102 curentry = ""
0103 if (debug) print "Processing file " FILENAME
0104 }
0105
0106 /^static struct ctl_path/ {
0107 match($0, /static struct ctl_path ([^][]+)/, tables)
0108 curpath = tables[1]
0109 if (debug) print "Processing path " curpath
0110 }
0111
0112 /^static struct ctl_table/ {
0113 match($0, /static struct ctl_table ([^][]+)/, tables)
0114 curtable = tables[1]
0115 if (debug) print "Processing table " curtable
0116 }
0117
0118 /^};$/ {
0119 curpath = ""
0120 curtable = ""
0121 curentry = ""
0122 }
0123
0124 curpath && /\.procname[\t ]*=[\t ]*".+"/ {
0125 match($0, /.procname[\t ]*=[\t ]*"([^"]+)"/, names)
0126 if (curentry) {
0127 curentry = curentry "/" names[1]
0128 } else {
0129 curentry = names[1]
0130 }
0131 if (debug) print "Setting path " curpath " to " curentry
0132 paths[curpath] = curentry
0133 }
0134
0135 curtable && /\.procname[\t ]*=[\t ]*".+"/ {
0136 match($0, /.procname[\t ]*=[\t ]*"([^"]+)"/, names)
0137 curentry = names[1]
0138 if (debug) print "Adding entry " curentry " to table " curtable
0139 entries[curtable][curentry]++
0140 file[curentry] = FILENAME
0141 }
0142
0143 /\.child[\t ]*=/ {
0144 child = trimpunct($NF)
0145 if (debug) print "Linking child " child " to table " curtable " entry " curentry
0146 children[curtable][curentry] = child
0147 }
0148
0149 /register_sysctl_table\(.*\)/ {
0150 match($0, /register_sysctl_table\(([^)]+)\)/, tables)
0151 if (debug) print "Registering table " tables[1]
0152 if (children[tables[1]][table]) {
0153 for (entry in entries[children[tables[1]][table]]) {
0154 printentry(entry)
0155 }
0156 }
0157 }
0158
0159 /register_sysctl_paths\(.*\)/ {
0160 match($0, /register_sysctl_paths\(([^)]+), ([^)]+)\)/, tables)
0161 if (debug) print "Attaching table " tables[2] " to path " tables[1]
0162 if (paths[tables[1]] == table) {
0163 for (entry in entries[tables[2]]) {
0164 printentry(entry)
0165 }
0166 }
0167 split(paths[tables[1]], components, "/")
0168 if (length(components) > 1 && components[1] == table) {
0169 # Count the first subdirectory as seen
0170 seen[components[2]]++
0171 }
0172 }
0173
0174
0175 END {
0176 for (entry in documented) {
0177 if (!seen[entry]) {
0178 print "No implementation for " entry
0179 }
0180 }
0181 }