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