Hi, Kenneth

Thanks for the patch! It looks a very helpful tool, I took a look over the code and found some minor problem:

On Sun, Aug 5, 2018 at 11:44 PM Kenneth Dsouza <kdsouza@redhat.com> wrote:
From: Kenneth D'souza <kdsouza@redhat.com>

A cmdline tool to edit and retrieve information from /etc/kdump.conf.
This tool will avoid typos and have basic sanity check for options.

Signed-off-by: Kenneth D'souza <kdsouza@redhat.com>
---
 kdump-config.py | 281 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 281 insertions(+)
 create mode 100755 kdump-config.py

diff --git a/kdump-config.py b/kdump-config.py
new file mode 100755
index 0000000..07b3201
--- /dev/null
+++ b/kdump-config.py
@@ -0,0 +1,281 @@
+#!/usr/bin/python
+# kdump-config.py - cmdline tool to display and manipulate configuration information of /etc/kdump.conf
+# Author: Kenneth D'souza <kdsouza@redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+import sys
+import os
+import stat
+import argparse
+import subprocess
+
+CONFIG_FILE = "/etc/kdump.conf"
+
+
+# Filter comments and optional settings.
+def filter_data(data):
+    comments = []
+    data_without_comments = []
+    for line in data:
+        line = line.strip()
+        if line.startswith('#'):
+            comments.append(line)
+        else:
+            data_without_comments.append(line)
+    return comments, data_without_comments
+
+
+# Get only unique data, avoids duplicate.
+def unique(data_without_comments, option):
+    unique_data = set()
+    for line in data_without_comments:
+        if len(line.split()) > 1:
+            if line.startswith(
+                ("path", "core_collector", "raw", "nfs", "ssh", "kdump_post",
+                 "kdump_pre", "extra_bins", "extra_modules", "sshkey",
+                 "default", "debug_mem_level", "force_rebuild",
+                 "force_no_rebuild", "dracut_args", "fence_kdump_args",
+                 "fence_kdump_nodes", "ext2", "ext3", "ext4", "btfs", "minix",
+                 "xfs")):
+                if line not in unique_data and not line.startswith(option + ' '):
+                    if option == "ssh":
+                        if not line.startswith(
+                            ("nfs", "raw", "ext4", "ext3", "ext2",
+                             "xfs", "btrfs", "minix")):
+                            unique_data.add(line)
+                    elif option == "nfs":
+                        if not line.startswith(
+                            ("ssh", "raw", "ext4", "ext3", "ext2", "xfs",
+                             "btrfs", "minix")):
+                            unique_data.add(line)
+                    elif option == "raw":
+                        if not line.startswith(
+                            ("nfs", "ssh", "ext4", "ext3", "ext2", "xfs",
+                             "btrfs", "minix")):
+                            unique_data.add(line)
+                    elif option in ("ext2", "ext3", "ext4", "xfs", "btrfs",
+                                     "minix"):
+                        if not line.startswith(("ssh", "nfs", "raw")):
+                            unique_data.add(line)
+                    else:
+                        unique_data.add(line)
+    return unique_data
+
+
+# We cannot have both the options set to be 1.
+def check_for_mutual_exculsive(unique_data):
+    for line in unique_data:
+        if line.startswith("force_no_rebuild") or line.startswith(
+                "force_rebuild"):
+            optional = line.split()[1]
+            if optional == value == "1":
+                print("force_no_rebuild and force_rebuild cannot be 1 at the same time.")
+                return True
+    return False
+
+
+# Check if ssh server is set, if not give warning.
+def checks_for_ssh(unique_data):
+    for line in unique_data:
+        if line.find("ssh") != -1:
I think it better be "startswith", it's possible that some config value have the word "ssh" in it 
+            return True
+    return False
+
+
+# Write changes to the file with comments, unique data and optional
+# settings given by the user.
+def replace_value_write(comments, unique_data, option, value, unset_value):
+    try:
+        with open(CONFIG_FILE, "r+") as f:
Why not use mode "w" for writing, and then seek() and truncate() will be not neccessary
+            f.seek(0)
+            f.truncate()
+            for item in comments:
+                f.write("%s\n" % item)
+            for i in range(len(unique_data)):
+                f.write("%s\n" % unique_data[i])
+            if not unset_value:
+                append_txt = ("%s %s\n" % (option, value))
+                f.write("%s" % append_txt)
+        f.close()
No need to call close when using "with" statement 
+    except IOError as reason:
+        print("You need to have root privileges to run this option.")
+        print("%s" % reason)
+
+
+# Read the file only once and save the data.
+def check(CONFIG_FILE, option, value, unset_value):
It's confusing to use same name "CONFIG_FILE" for function parameter and a global variable
+    with open(CONFIG_FILE, "r") as f1:
+        data = f1.readlines()
+        comments, data_without_comments = filter_data(data)
+        unique_data = list(unique(data_without_comments, option))
+        unique_data = list(filter(None, unique_data))
+        if option == "force_no_rebuild" or option == "force_rebuild" and value == "1":
+            result = check_for_mutual_exculsive(unique_data)
+            if result:
+                sys.exit(1)
+        if option == "sshkey":
+            result_ssh = checks_for_ssh(unique_data)
+            if not result_ssh:
+                print("Warning: Remember to add an entry for ssh")
+        replace_value_write(comments, unique_data, option, value, unset_value)
No need to call close when using "with" statement 
+    f1.close()
+
+
+# Check if the device is valid, check the option passed and work accordingly:
+def check_device(option, value):
+    try:
+        if not stat.S_ISBLK(os.lstat(value).st_mode):
+            print("not a valid device '%s'" % value)
+        elif option == "raw":
+            check(CONFIG_FILE, option, args.raw, False)
+        else:
+            cmd = subprocess.Popen(
+                "lsblk --nodeps -p -n -o FSTYPE,UUID %s " % (value),
+                stdout=subprocess.PIPE,
+                stderr=subprocess.STDOUT,
+                shell=True)
+            option = cmd.communicate()[0].strip().decode()
+            fstype, UUID = option.split()
+            if fstype in ("ext2", "ext3", "ext4", "xfs", "btrfs", "minix"):
+                check(CONFIG_FILE, fstype, "UUID=" + UUID, False)
+            else:
+                print("Filesystem '%s' not supported." % fstype)
+    except ValueError:
+        print("'%s' does not contain a valid Filesystem." % value)
+    except Exception:
+        print("not a valid device '%s'" % value)
+
+
+# Check if dump target is mounted:
+def check_mnt(nfs_mount):
+    cmd = subprocess.Popen(
+        "findmnt -t nfs,nfs4 %s -o SOURCE -n " % (nfs_mount),
+        stdout=subprocess.PIPE,
+        stderr=subprocess.STDOUT,
+        shell=True)
+    option = cmd.communicate()[0].strip().decode()
+    if not option.strip():
+        print("Error: Dump target '%s' is probably not mounted or valid" %
+              nfs_mount)
+    else:
+        check(CONFIG_FILE, "nfs", option, False)
+
+
+# Display configuration settings from /etc/kdump.conf:
+def display_show_config(data_without_comments):
+    for item in data_without_comments:
+        print(item)
+
+
+# Check if kdump config file exists.
+try:
+    lines = open(CONFIG_FILE).readlines()
+except IOError as reason:
+    print("Error reading kdump configuration.")
+    exit("%s" % reason)
+
+# Check if no arguments are given.
+if len(sys.argv) < 2:
+    exit("Please specify an action to perform. \nTry `kdump-config --help' for more information.")
+
+parser = argparse.ArgumentParser(
+    description="Please specify an action to perform,")
+parser.add_argument(
+    "--path",
+    help="path represents the file system path in which vmcore will be saved.")
+parser.add_argument("--raw", help=" specify raw device ex: /dev/sdc1")
+parser.add_argument(
+    "--nfs", help="specify nfs-server ex: my.server.com:/export/tmp")
+parser.add_argument("--ssh", help="specify ssh server ex: user@my.server.com")
+parser.add_argument(
+    "--sshkey", help="specify the path of the ssh key to use when dumping.")
+parser.add_argument(
+    "--showconfig", action="store_true", help="show configuration settings.")
+parser.add_argument(
+    "--local_fs", help="specify local device ex: /dev/myvg/mylv")
+parser.add_argument("--unset", help="unset a given option ex: path")
+parser.add_argument(
+    "--force_no_rebuild",
+    choices=["1", "0"],
+    help="specify 1 to bypass rebuilding of kdump initrd.")
+parser.add_argument(
+    "--force_rebuild",
+    choices=["1", "0"],
+    help="specify 1 to force rebuilding kdump initrd every time when kdump service starts.")
+parser.add_argument(
+    "--default",
+    choices=["reboot", "halt", "poweroff", "shell", "dump_to_rootfs"],
+    help="Action to perform in case dumping to the intended target fails.")
+
+args = parser.parse_args()
+
+if args.default:
+    option = "default"
+    value = args.default
+    check(CONFIG_FILE, option, value, False)
+
+if args.path:
+    option = "path"
+    value = args.path
+    check(CONFIG_FILE, option, value, False)
+
+if args.showconfig:
+    with open(CONFIG_FILE, "r") as f2:
+        data = f2.readlines()
+        comments, data_without_comments = filter_data(data)
+        display_show_config(data_without_comments)
+
+if args.raw:
+    option = "raw"
+    device = os.path.realpath(args.raw)
+    check_device(option, device)
+
+if args.force_rebuild:
+    option = "force_rebuild"
+    value = args.force_rebuild
+    check(CONFIG_FILE, option, value, False)
+
+if args.force_no_rebuild:
+    option = "force_no_rebuild"
+    value = args.force_no_rebuild
+    check(CONFIG_FILE, option, value, False)
+
+if args.nfs:
+    option = "nfs"
+    value = args.nfs
+    check_mnt(value)
+
+if args.ssh:
+    option = "ssh"
+    value = args.ssh
+    check(CONFIG_FILE, option, value, False)
+
+if args.sshkey:
+    if not os.path.isfile(args.sshkey):
+        exit("Invalid file '%s'" % args.sshkey)
+    option = "sshkey"
+    value = args.sshkey
+    check(CONFIG_FILE, option, value, False)
+
+if args.local_fs:
+    device = os.path.realpath(args.local_fs)
+    option = "local"
+    check_device(option, device)
+
+if args.unset:
+    option = args.unset
+    value = ""
+    check(CONFIG_FILE, option, value, True)
--
2.14.3
_______________________________________________
kexec mailing list -- kexec@lists.fedoraproject.org
To unsubscribe send an email to kexec-leave@lists.fedoraproject.org
Fedora Code of Conduct: https://getfedora.org/code-of-conduct.html
List Guidelines: https://fedoraproject.org/wiki/Mailing_list_guidelines
List Archives: https://lists.fedoraproject.org/archives/list/kexec@lists.fedoraproject.org/message/7XCXNVKI2FBS5L2OOHYVFXBSIB535U3C/

Another issue is that when called with multiple argument, this script will open and rewrite the whole file several times, overhead is trivial, but still will be better to avoid this.
And it always break the origin order of comments and config content, and filter out every invalid lines or configs silently, not sure if this is an acceptable manner for everyone.

For the script itself, if it's included in kexec-tools, will need to make it a sub-package as it requires python, and it's noarch. Some noarch python components were just moved out of the main package into standalone package some time ago, so I think maybe it will be better to be kept as a standalone package for easier maintaining.

--
Best Regards,
Kairui Song