1#!/usr/bin/gawk -f 2# SPDX-License-Identifier: GPL-2.0 3 4# Script to check sysctl documentation against source files 5# 6# Copyright (c) 2020 Stephen Kitt 7 8# Example invocation: 9# scripts/check-sysctl-docs -vtable="kernel" \ 10# Documentation/admin-guide/sysctl/kernel.rst \ 11# $(git grep -l register_sysctl) 12# 13# Specify -vdebug=1 to see debugging information 14 15BEGIN { 16 if (!table) { 17 print "Please specify the table to look for using the table variable" > "/dev/stderr" 18 exit 1 19 } 20} 21 22# The following globals are used: 23# documented: maps documented entries (each key is an entry) 24# entries: maps ctl_table names and procnames to counts (so 25# enumerating the subkeys for a given ctl_table lists its 26# procnames) 27# curtable: the name of the current ctl_table struct 28# curentry: the name of the current proc entry (procname when parsing 29# a ctl_table, constructed path when parsing a ctl_path) 30 31 32# Remove punctuation from the given value 33function trimpunct(value) { 34 while (value ~ /^["&]/) { 35 value = substr(value, 2) 36 } 37 while (value ~ /[]["&,}]$/) { 38 value = substr(value, 1, length(value) - 1) 39 } 40 return value 41} 42 43# Print the information for the given entry 44function printentry(entry) { 45 seen[entry]++ 46 printf "* %s from %s", entry, file[entry] 47 if (documented[entry]) { 48 printf " (documented)" 49 } 50 print "" 51} 52 53 54# Stage 1: build the list of documented entries 55FNR == NR && /^=+$/ { 56 if (prevline ~ /Documentation for/) { 57 # This is the main title 58 next 59 } 60 61 # The previous line is a section title, parse it 62 $0 = prevline 63 if (debug) print "Parsing " $0 64 inbrackets = 0 65 for (i = 1; i <= NF; i++) { 66 if (length($i) == 0) { 67 continue 68 } 69 if (!inbrackets && substr($i, 1, 1) == "(") { 70 inbrackets = 1 71 } 72 if (!inbrackets) { 73 token = trimpunct($i) 74 if (length(token) > 0 && token != "and") { 75 if (debug) print trimpunct($i) 76 documented[trimpunct($i)]++ 77 } 78 } 79 if (inbrackets && substr($i, length($i), 1) == ")") { 80 inbrackets = 0 81 } 82 } 83} 84 85FNR == NR { 86 prevline = $0 87 next 88} 89 90 91# Stage 2: process each file and find all sysctl tables 92BEGINFILE { 93 delete entries 94 curtable = "" 95 curentry = "" 96 delete vars 97 if (debug) print "Processing file " FILENAME 98} 99 100/^static( const)? struct ctl_table/ { 101 match($0, /static( const)? struct ctl_table ([^][]+)/, tables) 102 curtable = tables[2] 103 if (debug) print "Processing table " curtable 104} 105 106/^};$/ { 107 curtable = "" 108 curentry = "" 109 delete vars 110} 111 112curtable && /\.procname[\t ]*=[\t ]*".+"/ { 113 match($0, /.procname[\t ]*=[\t ]*"([^"]+)"/, names) 114 curentry = names[1] 115 if (debug) print "Adding entry " curentry " to table " curtable 116 entries[curtable][curentry]++ 117 file[curentry] = FILENAME 118} 119 120/register_sysctl.*/ { 121 match($0, /register_sysctl(|_init|_sz)\("([^"]+)" *, *([^,)]+)/, tables) 122 if (debug) print "Registering table " tables[3] " at " tables[2] 123 if (tables[2] == table) { 124 for (entry in entries[tables[3]]) { 125 printentry(entry) 126 } 127 } 128} 129 130/kmemdup.*/ { 131 match($0, /([^ \t]+) *= *kmemdup\(([^,]+) *,/, names) 132 if (debug) print "Found variable " names[1] " for table " names[2] 133 if (names[2] in entries) { 134 vars[names[1]] = names[2] 135 } 136} 137 138/__register_sysctl_table.*/ { 139 match($0, /__register_sysctl_table\([^,]+, *"([^"]+)" *, *([^,]+)/, tables) 140 if (debug) print "Registering variable table " tables[2] " at " tables[1] 141 if (tables[1] == table && tables[2] in vars) { 142 for (entry in entries[vars[tables[2]]]) { 143 printentry(entry) 144 } 145 } 146} 147 148END { 149 for (entry in documented) { 150 if (!seen[entry]) { 151 print "No implementation for " entry 152 } 153 } 154} 155