Blame checkopts

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