Blame SOURCES/0004-checkopts-add-python-script-to-cross-check-mount-opt.patch

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