Blame checkopts

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