linux/scripts/check-sysctl-docs
<<
>>
Prefs
   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# children: maps ctl_table names and procnames to child ctl_table names
  24# documented: maps documented entries (each key is an entry)
  25# entries: maps ctl_table names and procnames to counts (so
  26#          enumerating the subkeys for a given ctl_table lists its
  27#          procnames)
  28# files: maps procnames to source file names
  29# paths: maps ctl_path names to paths
  30# curpath: the name of the current ctl_path struct
  31# curtable: the name of the current ctl_table struct
  32# curentry: the name of the current proc entry (procname when parsing
  33#           a ctl_table, constructed path when parsing a ctl_path)
  34
  35
  36# Remove punctuation from the given value
  37function trimpunct(value) {
  38    while (value ~ /^["&]/) {
  39        value = substr(value, 2)
  40    }
  41    while (value ~ /[]["&,}]$/) {
  42        value = substr(value, 1, length(value) - 1)
  43    }
  44    return value
  45}
  46
  47# Print the information for the given entry
  48function printentry(entry) {
  49    seen[entry]++
  50    printf "* %s from %s", entry, file[entry]
  51    if (documented[entry]) {
  52        printf " (documented)"
  53    }
  54    print ""
  55}
  56
  57
  58# Stage 1: build the list of documented entries
  59FNR == NR && /^=+$/ {
  60    if (prevline ~ /Documentation for/) {
  61        # This is the main title
  62        next
  63    }
  64
  65    # The previous line is a section title, parse it
  66    $0 = prevline
  67    if (debug) print "Parsing " $0
  68    inbrackets = 0
  69    for (i = 1; i <= NF; i++) {
  70        if (length($i) == 0) {
  71            continue
  72        }
  73        if (!inbrackets && substr($i, 1, 1) == "(") {
  74            inbrackets = 1
  75        }
  76        if (!inbrackets) {
  77            token = trimpunct($i)
  78            if (length(token) > 0 && token != "and") {
  79                if (debug) print trimpunct($i)
  80                documented[trimpunct($i)]++
  81            }
  82        }
  83        if (inbrackets && substr($i, length($i), 1) == ")") {
  84            inbrackets = 0
  85        }
  86    }
  87}
  88
  89FNR == NR {
  90    prevline = $0
  91    next
  92}
  93
  94
  95# Stage 2: process each file and find all sysctl tables
  96BEGINFILE {
  97    delete children
  98    delete entries
  99    delete paths
 100    curpath = ""
 101    curtable = ""
 102    curentry = ""
 103    if (debug) print "Processing file " FILENAME
 104}
 105
 106/^static struct ctl_path/ {
 107    match($0, /static struct ctl_path ([^][]+)/, tables)
 108    curpath = tables[1]
 109    if (debug) print "Processing path " curpath
 110}
 111
 112/^static struct ctl_table/ {
 113    match($0, /static struct ctl_table ([^][]+)/, tables)
 114    curtable = tables[1]
 115    if (debug) print "Processing table " curtable
 116}
 117
 118/^};$/ {
 119    curpath = ""
 120    curtable = ""
 121    curentry = ""
 122}
 123
 124curpath && /\.procname[\t ]*=[\t ]*".+"/ {
 125    match($0, /.procname[\t ]*=[\t ]*"([^"]+)"/, names)
 126    if (curentry) {
 127        curentry = curentry "/" names[1]
 128    } else {
 129        curentry = names[1]
 130    }
 131    if (debug) print "Setting path " curpath " to " curentry
 132    paths[curpath] = curentry
 133}
 134
 135curtable && /\.procname[\t ]*=[\t ]*".+"/ {
 136    match($0, /.procname[\t ]*=[\t ]*"([^"]+)"/, names)
 137    curentry = names[1]
 138    if (debug) print "Adding entry " curentry " to table " curtable
 139    entries[curtable][curentry]++
 140    file[curentry] = FILENAME
 141}
 142
 143/\.child[\t ]*=/ {
 144    child = trimpunct($NF)
 145    if (debug) print "Linking child " child " to table " curtable " entry " curentry
 146    children[curtable][curentry] = child
 147}
 148
 149/register_sysctl_table\(.*\)/ {
 150    match($0, /register_sysctl_table\(([^)]+)\)/, tables)
 151    if (debug) print "Registering table " tables[1]
 152    if (children[tables[1]][table]) {
 153        for (entry in entries[children[tables[1]][table]]) {
 154            printentry(entry)
 155        }
 156    }
 157}
 158
 159/register_sysctl_paths\(.*\)/ {
 160    match($0, /register_sysctl_paths\(([^)]+), ([^)]+)\)/, tables)
 161    if (debug) print "Attaching table " tables[2] " to path " tables[1]
 162    if (paths[tables[1]] == table) {
 163        for (entry in entries[tables[2]]) {
 164            printentry(entry)
 165        }
 166    }
 167    split(paths[tables[1]], components, "/")
 168    if (length(components) > 1 && components[1] == table) {
 169        # Count the first subdirectory as seen
 170        seen[components[2]]++
 171    }
 172}
 173
 174
 175END {
 176    for (entry in documented) {
 177        if (!seen[entry]) {
 178            print "No implementation for " entry
 179        }
 180    }
 181}
 182