Blame checkopts

Packit 139f75
#!/usr/bin/env python3
Packit 139f75
#
Packit 139f75
# Script to check for inconsistencies between documented mount options
Packit 139f75
# and implemented kernel options.
Packit 139f75
# Copyright (C) 2018 Aurelien Aptel (aaptel@suse.com)
Packit 139f75
#
Packit 139f75
# This program is free software; you can redistribute it and/or modify
Packit 139f75
# it under the terms of the GNU General Public License as published by
Packit 139f75
# the Free Software Foundation; either version 3 of the License, or
Packit 139f75
# (at your option) any later version.
Packit 139f75
#
Packit 139f75
# This program is distributed in the hope that it will be useful,
Packit 139f75
# but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 139f75
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 139f75
# GNU General Public License for more details.
Packit 139f75
#
Packit 139f75
# You should have received a copy of the GNU General Public License
Packit 139f75
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
Packit 139f75
Packit 139f75
import os
Packit 139f75
import sys
Packit 139f75
import re
Packit 139f75
import subprocess
Packit 139f75
import argparse
Packit 139f75
from pprint import pprint as P
Packit 139f75
Packit 139f75
def extract_canonical_opts(s):
Packit 139f75
    """
Packit 139f75
    Return list of option names present in s.
Packit 139f75
    e.g "opt1=a|opt2=d" => ["opt1", "opt2"])
Packit 139f75
    """
Packit 139f75
    opts = s.split("|")
Packit 139f75
    res = []
Packit 139f75
    for o in opts:
Packit 139f75
        x = o.split("=")
Packit 139f75
        res.append(x[0])
Packit 139f75
    return res
Packit 139f75
Packit 139f75
def extract_kernel_opts(fn):
Packit 139f75
    STATE_BASE = 0
Packit 139f75
    STATE_DEF = 1
Packit 139f75
    STATE_USE = 2
Packit 139f75
    STATE_EXIT = 3
Packit 139f75
Packit 139f75
    state = STATE_BASE
Packit 139f75
    fmt2enum = {}
Packit 139f75
    enum2code = {}
Packit 139f75
    code = ''
Packit 139f75
    current_opt = ''
Packit 139f75
    rx = RX()
Packit 139f75
Packit 139f75
    def code_add(s):
Packit 139f75
        if current_opt != '':
Packit 139f75
            if current_opt not in enum2code:
Packit 139f75
                enum2code[current_opt] = ''
Packit 139f75
            enum2code[current_opt] += s
Packit 139f75
Packit 139f75
    with open(fn) as f:
Packit 139f75
        for s in f.readlines():
Packit 139f75
            if state == STATE_EXIT:
Packit 139f75
                break
Packit 139f75
Packit 139f75
            elif state == STATE_BASE:
Packit 139f75
                if rx.search(r'cifs_mount_option_tokens.*\{', s):
Packit 139f75
                    state = STATE_DEF
Packit 139f75
                elif rx.search(r'^cifs_parse_mount_options', s):
Packit 139f75
                    state = STATE_USE
Packit 139f75
Packit 139f75
            elif state == STATE_DEF:
Packit 139f75
                if rx.search(r'(Opt_[a-zA-Z0-9_]+)\s*,\s*"([^"]+)"', s):
Packit 139f75
                    fmt = rx.group(2)
Packit 139f75
                    opts = extract_canonical_opts(fmt)
Packit 139f75
                    assert(len(opts) == 1)
Packit 139f75
                    name = opts[0]
Packit 139f75
                    fmt2enum[name] = {'enum':rx.group(1), 'fmt':fmt}
Packit 139f75
                elif rx.search(r'^};', s):
Packit 139f75
                    state = STATE_BASE
Packit 139f75
Packit 139f75
            elif state == STATE_USE:
Packit 139f75
                if rx.search(r'^\s*case (Opt_[a-zA-Z0-9_]+)', s):
Packit 139f75
                    current_opt = rx.group(1)
Packit 139f75
                elif current_opt != '' and rx.search(r'^\s*default:', s):
Packit 139f75
                    state = STATE_EXIT
Packit 139f75
                else:
Packit 139f75
                    code_add(s)
Packit 139f75
    return fmt2enum, enum2code
Packit 139f75
Packit 139f75
def chomp(s):
Packit 139f75
    if s[-1] == '\n':
Packit 139f75
        return s[:-1]
Packit 139f75
    return s
Packit 139f75
Packit 139f75
def extract_man_opts(fn):
Packit 139f75
    STATE_EXIT = 0
Packit 139f75
    STATE_BASE = 1
Packit 139f75
    STATE_OPT = 2
Packit 139f75
Packit 139f75
    state = STATE_BASE
Packit 139f75
    rx = RX()
Packit 139f75
    opts = {}
Packit cd5a94
    ln = 0
Packit 139f75
Packit 139f75
    with open(fn) as f:
Packit 139f75
        for s in f.readlines():
Packit cd5a94
            ln += 1
Packit cd5a94
Packit 139f75
            if state == STATE_EXIT:
Packit 139f75
                break
Packit 139f75
Packit 139f75
            elif state == STATE_BASE:
Packit 139f75
                if rx.search(r'^OPTION', s):
Packit 139f75
                    state = STATE_OPT
Packit 139f75
Packit 139f75
            elif state == STATE_OPT:
Packit 139f75
                if rx.search('^[a-z]', s) and len(s) < 50:
Packit 139f75
                    s = chomp(s)
Packit 139f75
                    names = extract_canonical_opts(s)
Packit 139f75
                    for name in names:
Packit cd5a94
                        if name not in opts:
Packit cd5a94
                            opts[name] = []
Packit cd5a94
                        opts[name].append({'ln':ln, 'fmt':s})
Packit 139f75
                elif rx.search(r'^[A-Z]+', s):
Packit 139f75
                    state = STATE_EXIT
Packit 139f75
    return opts
Packit 139f75
Packit 139f75
def format_code(s):
Packit 139f75
    # remove common indent in the block
Packit 139f75
    min_indent = None
Packit 139f75
    for ln in s.split("\n"):
Packit 139f75
        indent = 0
Packit 139f75
        for c in ln:
Packit 139f75
            if c == '\t': indent += 1
Packit 139f75
            else: break
Packit 139f75
        if min_indent is None:
Packit 139f75
            min_indent = indent
Packit 139f75
        elif indent > 0:
Packit 139f75
            min_indent = min(indent, min_indent)
Packit 139f75
    out = ''
Packit 139f75
    lines = s.split("\n")
Packit 139f75
    if lines[-1].strip() == '':
Packit 139f75
        lines.pop()
Packit 139f75
    for ln in lines:
Packit 139f75
        out += "| %s\n" % ln[min_indent:]
Packit 139f75
    return out
Packit 139f75
Packit 139f75
def sortedset(s):
Packit 139f75
    return sorted(list(s), key=lambda x: re.sub('^no', '', x))
Packit 139f75
Packit 139f75
def opt_neg(opt):
Packit 139f75
    if opt.startswith("no"):
Packit 139f75
        return opt[2:]
Packit 139f75
    else:
Packit 139f75
        return "no"+opt
Packit 139f75
Packit 139f75
def main():
Packit 139f75
    ap = argparse.ArgumentParser(description="Cross-check mount options from cifs.ko/man page")
Packit 139f75
    ap.add_argument("cfile", help="path to connect.c")
Packit 139f75
    ap.add_argument("rstfile", help="path to mount.cifs.rst")
Packit 139f75
    args = ap.parse_args()
Packit 139f75
Packit 139f75
    fmt2enum, enum2code = extract_kernel_opts(args.cfile)
Packit 139f75
    manopts = extract_man_opts(args.rstfile)
Packit 139f75
Packit 139f75
    kernel_opts_set = set(fmt2enum.keys())
Packit 139f75
    man_opts_set = set(manopts.keys())
Packit 139f75
Packit 139f75
    def opt_alias_is_doc(o):
Packit 139f75
        enum = fmt2enum[o]['enum']
Packit 139f75
        aliases = []
Packit 139f75
        for k,v in fmt2enum.items():
Packit 139f75
            if k != o and v['enum'] == enum:
Packit 139f75
                if opt_is_doc(k):
Packit 139f75
                    return k
Packit 139f75
        return None
Packit 139f75
Packit 139f75
    def opt_exists(o):
Packit 139f75
        return o in fmt2enum
Packit 139f75
Packit 139f75
    def opt_is_doc(o):
Packit 139f75
        return o in manopts
Packit 139f75
Packit cd5a94
    print('DUPLICATED DOC OPTIONS')
Packit cd5a94
    print('======================')
Packit cd5a94
Packit cd5a94
    for opt in sortedset(man_opts_set):
Packit cd5a94
        if len(manopts[opt]) > 1:
Packit cd5a94
            lines = ", ".join([str(x['ln']) for x in manopts[opt]])
Packit cd5a94
            print("OPTION %-20.20s (lines %s)"%(opt, lines))
Packit cd5a94
    print()
Packit 139f75
Packit 139f75
    print('UNDOCUMENTED OPTIONS')
Packit 139f75
    print('====================')
Packit 139f75
Packit 139f75
    undoc_opts = kernel_opts_set - man_opts_set
Packit 139f75
    # group opts and their negations together
Packit 139f75
    for opt in sortedset(undoc_opts):
Packit 139f75
        fmt = fmt2enum[opt]['fmt']
Packit 139f75
        enum = fmt2enum[opt]['enum']
Packit 139f75
        code = format_code(enum2code[enum])
Packit 139f75
        neg = opt_neg(opt)
Packit 139f75
Packit 139f75
        if enum == 'Opt_ignore':
Packit 139f75
            print("# skipping %s (Opt_ignore)\n"%opt)
Packit 139f75
            continue
Packit 139f75
Packit 139f75
        if opt_exists(neg) and opt_is_doc(neg):
Packit 139f75
            print("# skipping %s (%s is documented)\n"%(opt, neg))
Packit 139f75
            continue
Packit 139f75
Packit 139f75
        alias = opt_alias_is_doc(opt)
Packit 139f75
        if alias:
Packit 139f75
            print("# skipping %s (alias %s is documented)\n"%(opt, alias))
Packit 139f75
            continue
Packit 139f75
Packit 139f75
        print('OPTION %s ("%s" -> %s):\n%s'%(opt, fmt, enum, code))
Packit 139f75
Packit 139f75
    print('')
Packit 139f75
    print('DOCUMENTED BUT NON-EXISTING OPTIONS')
Packit 139f75
    print('===================================')
Packit 139f75
Packit 139f75
    unex_opts = man_opts_set - kernel_opts_set
Packit 139f75
    # group opts and their negations together
Packit 139f75
    for opt in sortedset(unex_opts):
Packit cd5a94
        man = manopts[opt][0]
Packit cd5a94
        print('OPTION %s ("%s") line %d' % (opt, man['fmt'], man['ln']))
Packit 139f75
Packit 139f75
Packit 139f75
    print('')
Packit 139f75
    print('NEGATIVE OPTIONS WITHOUT POSITIVE')
Packit 139f75
    print('=================================')
Packit 139f75
Packit 139f75
    for opt in sortedset(kernel_opts_set):
Packit 139f75
        if not opt.startswith('no'):
Packit 139f75
            continue
Packit 139f75
Packit 139f75
        neg = opt[2:]
Packit 139f75
        if not opt_exists(neg):
Packit 139f75
            print("OPTION %s exists but not %s"%(opt,neg))
Packit 139f75
Packit 139f75
# little helper to test AND store result at the same time so you can
Packit 139f75
# do if/elsif easily instead of nesting them when you need to do
Packit 139f75
# captures
Packit 139f75
class RX:
Packit 139f75
    def __init__(self):
Packit 139f75
        pass
Packit 139f75
    def search(self, rx, s, flags=0):
Packit 139f75
        self.r = re.search(rx, s, flags)
Packit 139f75
        return self.r
Packit 139f75
    def group(self, n):
Packit 139f75
        return self.r.group(n)
Packit 139f75
Packit 139f75
if __name__ == '__main__':
Packit 139f75
    main()