We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Need to enhance parser "ls_parser" to parse the output of  command 'ls -laRZ <dir-name>' in RHEL-8 
The parser **ls_parser** needs to be enhanced to support parsing the output of command `ls` with an option `Z` (for selinux context) in a RHEL8 system. In RHEL8, additional fields have been introduced in the output of command `ls -laRZ`. For example:

RHEL7: 

```
#/bin/ls -laRZ /var/lib/nova/instances
/var/lib/nova/instances:
drwxr-xr-x. nova nova system_u:object_r:nova_var_lib_t:s0 .
drwxr-xr-x. nova nova system_u:object_r:nova_var_lib_t:s0 ..
drwxr-xr-x. root root unconfined_u:object_r:nova_var_lib_t:s0 xxxx-xxxx-xxxx-xxxx
```

RHEL8: 

```
#/bin/ls -laRZ /var/lib/nova/instances
/var/lib/nova/instances:
total 0
drwxr-xr-x. 3 root root unconfined_u:object_r:var_lib_t:s0 50 Apr  8 16:41 .
drwxr-xr-x. 3 root root unconfined_u:object_r:var_lib_t:s0 23 Apr  8 16:29 ..
drwxr-xr-x. 2 root root unconfined_u:object_r:var_lib_t:s0 54 Apr  8 16:41 xxxx-xxxx-xxxx-xxxx
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `insights/core/ls_parser.py`
Content:
```
1 """
2 This module contains logic for parsing ls output. It attempts to handle
3 output when selinux is enabled or disabled and also skip "bad" lines.
4 """
5 import six
6 
7 
8 def parse_path(path):
9     """
10     Convert possible symbolic link into a source -> target pair.
11 
12     Args:
13         path (str): The path portion of an ls output line.
14 
15     Returns:
16         A (path, link) tuple where path is always populated and link is a non
17         empty string if the original path is a symoblic link.
18     """
19     path, _, link = path.partition(" -> ")
20     return path, link
21 
22 
23 def parse_non_selinux(parts):
24     """
25     Parse part of an ls output line that isn't selinux.
26 
27     Args:
28         parts (list): A four element list of strings representing the initial
29             parts of an ls line after the permission bits. The parts are link
30             count, owner, group, and everything else.
31 
32     Returns:
33         A dict containing links, owner, group, date, and name. If the line
34         represented a device, major and minor numbers are included.  Otherwise,
35         size is included. If the raw name was a symbolic link, link is
36         included.
37     """
38     links, owner, group, last = parts
39     result = {
40         "links": int(links),
41         "owner": owner,
42         "group": group,
43     }
44 
45     # device numbers only go to 256.
46     # If a comma is in the first four characters, the next two elements are
47     # major and minor device numbers. Otherwise, the next element is the size.
48     if "," in last[:4]:
49         major, minor, rest = last.split(None, 2)
50         result["major"] = int(major.rstrip(","))
51         result["minor"] = int(minor)
52     else:
53         size, rest = last.split(None, 1)
54         result["size"] = int(size)
55 
56     # The date part is always 12 characters regardless of content.
57     result["date"] = rest[:12]
58 
59     # Jump over the date and the following space to get the path part.
60     path, link = parse_path(rest[13:])
61     result["name"] = path
62     if link:
63         result["link"] = link
64 
65     return result
66 
67 
68 def parse_selinux(parts):
69     """
70     Parse part of an ls output line that is selinux.
71 
72     Args:
73         parts (list): A four element list of strings representing the initial
74             parts of an ls line after the permission bits. The parts are owner
75             group, selinux info, and the path.
76 
77     Returns:
78         A dict containing owner, group, se_user, se_role, se_type, se_mls, and
79         name. If the raw name was a symbolic link, link is always included.
80 
81     """
82 
83     owner, group = parts[:2]
84     selinux = parts[2].split(":")
85     lsel = len(selinux)
86     path, link = parse_path(parts[-1])
87     result = {
88         "owner": owner,
89         "group": group,
90         "se_user": selinux[0],
91         "se_role": selinux[1] if lsel > 1 else None,
92         "se_type": selinux[2] if lsel > 2 else None,
93         "se_mls": selinux[3] if lsel > 3 else None,
94         "name": path
95     }
96     if link:
97         result["link"] = link
98     return result
99 
100 
101 PASS_KEYS = set(["name", "total"])
102 DELAYED_KEYS = ["entries", "files", "dirs", "specials"]
103 
104 
105 class Directory(dict):
106     def __init__(self, name, total, body):
107         data = dict.fromkeys(DELAYED_KEYS)
108         data["name"] = name
109         data["total"] = total
110         self.body = body
111         self.loaded = False
112         super(Directory, self).__init__(data)
113 
114     def iteritems(self):
115         if not self.loaded:
116             self._load()
117         return six.iteritems(super(Directory, self))
118 
119     def items(self):
120         if not self.loaded:
121             self._load()
122         return super(Directory, self).items()
123 
124     def values(self):
125         if not self.loaded:
126             self._load()
127         return super(Directory, self).values()
128 
129     def get(self, key, default=None):
130         if not self.loaded:
131             self._load()
132         return super(Directory, self).get(key, default)
133 
134     def _load(self):
135         dirs = []
136         ents = {}
137         files = []
138         specials = []
139         for line in self.body:
140             parts = line.split(None, 4)
141             perms = parts[0]
142             typ = perms[0]
143             entry = {
144                 "type": typ,
145                 "perms": perms[1:]
146             }
147             if parts[1][0].isdigit():
148                 rest = parse_non_selinux(parts[1:])
149             else:
150                 rest = parse_selinux(parts[1:])
151 
152             # Update our entry and put it into the correct buckets
153             # based on its type.
154             entry.update(rest)
155             entry["raw_entry"] = line
156             entry["dir"] = self["name"]
157             nm = entry["name"]
158             ents[nm] = entry
159             if typ not in "bcd":
160                 files.append(nm)
161             elif typ == "d":
162                 dirs.append(nm)
163             elif typ in "bc":
164                 specials.append(nm)
165 
166         self.update({"entries": ents,
167                      "files": files,
168                      "dirs": dirs,
169                      "specials": specials})
170 
171         self.loaded = True
172         del self.body
173 
174     def __getitem__(self, key):
175         if self.loaded or key in PASS_KEYS:
176             return super(Directory, self).__getitem__(key)
177         self._load()
178         return super(Directory, self).__getitem__(key)
179 
180 
181 def parse(lines, root=None):
182     """
183     Parses a list of lines from ls into dictionaries representing their
184     components.
185 
186     Args:
187         lines (list): A list of lines generated by ls.
188         root (str): The directory name to be used for ls output stanzas that
189             don't have a name.
190 
191     Returns:
192         A dictionary representing the ls output. It's keyed by the path
193         containing each ls stanza.
194     """
195     doc = {}
196     entries = []
197     name = None
198     total = None
199     for line in lines:
200         line = line.strip()
201         if not line:
202             continue
203         if line and line[0] == "/" and line[-1] == ":":
204             if name is None:
205                 name = line[:-1]
206                 if entries:
207                     d = Directory(name, total or len(entries), entries)
208                     doc[root] = d
209                     total = None
210                     entries = []
211             else:
212                 d = Directory(name, total or len(entries), entries)
213                 doc[name or root] = d
214                 total = None
215                 entries = []
216                 name = line[:-1]
217             continue
218         if line.startswith("total"):
219             total = int(line.split(None, 1)[1])
220             continue
221         entries.append(line)
222     name = name or root
223     doc[name] = Directory(name, total or len(entries), entries)
224     return doc
225
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/insights/core/ls_parser.py b/insights/core/ls_parser.py
--- a/insights/core/ls_parser.py
+++ b/insights/core/ls_parser.py
@@ -76,7 +76,7 @@
 
     Returns:
         A dict containing owner, group, se_user, se_role, se_type, se_mls, and
-        name. If the raw name was a symbolic link, link is always included.
+        name. If the raw name was a symbolic link, link is also included.
 
     """
 
@@ -98,6 +98,47 @@
     return result
 
 
+def parse_rhel8_selinux(parts):
+    """
+    Parse part of an ls output line that is selinux on RHEL8.
+
+    Args:
+        parts (list): A four element list of strings representing the initial
+            parts of an ls line after the permission bits. The parts are link
+            count, owner, group, and everything else
+
+    Returns:
+        A dict containing links, owner, group, se_user, se_role, se_type,
+        se_mls, size, date, and name. If the raw name was a symbolic link,
+        link is also included.
+
+    """
+
+    links, owner, group, last = parts
+
+    selinux = parts[3].split(":")
+    lsel = len(selinux)
+    selinux, size, last = parts[-1].split(None, 2)
+    selinux = selinux.split(":")
+    date = last[:12]
+    path, link = parse_path(last[13:])
+    result = {
+        "links": int(links),
+        "owner": owner,
+        "group": group,
+        "se_user": selinux[0],
+        "se_role": selinux[1] if lsel > 1 else None,
+        "se_type": selinux[2] if lsel > 2 else None,
+        "se_mls": selinux[3] if lsel > 3 else None,
+        "size": int(size),
+        "name": path,
+        "date": date,
+    }
+    if link:
+        result["link"] = link
+    return result
+
+
 PASS_KEYS = set(["name", "total"])
 DELAYED_KEYS = ["entries", "files", "dirs", "specials"]
 
@@ -137,6 +178,10 @@
         files = []
         specials = []
         for line in self.body:
+            # we can't split(None, 5) here b/c rhel 6/7 selinux lines only have
+            # 4 parts before the path, and the path itself could contain
+            # spaces. Unfortunately, this means we have to split the line again
+            # below
             parts = line.split(None, 4)
             perms = parts[0]
             typ = perms[0]
@@ -145,7 +190,13 @@
                 "perms": perms[1:]
             }
             if parts[1][0].isdigit():
-                rest = parse_non_selinux(parts[1:])
+                # We have to split the line again to see if this is a RHEL8
+                # selinux stanza. This assumes that the context section will
+                # always have at least two pieces separated by ':'.
+                if ":" in line.split()[4]:
+                    rest = parse_rhel8_selinux(parts[1:])
+                else:
+                    rest = parse_non_selinux(parts[1:])
             else:
                 rest = parse_selinux(parts[1:])
 
@@ -220,5 +271,6 @@
             continue
         entries.append(line)
     name = name or root
-    doc[name] = Directory(name, total or len(entries), entries)
+    total = total if total is not None else len(entries)
+    doc[name] = Directory(name, total, entries)
     return doc

{"golden_diff": "diff --git a/insights/core/ls_parser.py b/insights/core/ls_parser.py\n--- a/insights/core/ls_parser.py\n+++ b/insights/core/ls_parser.py\n@@ -76,7 +76,7 @@\n \n     Returns:\n         A dict containing owner, group, se_user, se_role, se_type, se_mls, and\n-        name. If the raw name was a symbolic link, link is always included.\n+        name. If the raw name was a symbolic link, link is also included.\n \n     \"\"\"\n \n@@ -98,6 +98,47 @@\n     return result\n \n \n+def parse_rhel8_selinux(parts):\n+    \"\"\"\n+    Parse part of an ls output line that is selinux on RHEL8.\n+\n+    Args:\n+        parts (list): A four element list of strings representing the initial\n+            parts of an ls line after the permission bits. The parts are link\n+            count, owner, group, and everything else\n+\n+    Returns:\n+        A dict containing links, owner, group, se_user, se_role, se_type,\n+        se_mls, size, date, and name. If the raw name was a symbolic link,\n+        link is also included.\n+\n+    \"\"\"\n+\n+    links, owner, group, last = parts\n+\n+    selinux = parts[3].split(\":\")\n+    lsel = len(selinux)\n+    selinux, size, last = parts[-1].split(None, 2)\n+    selinux = selinux.split(\":\")\n+    date = last[:12]\n+    path, link = parse_path(last[13:])\n+    result = {\n+        \"links\": int(links),\n+        \"owner\": owner,\n+        \"group\": group,\n+        \"se_user\": selinux[0],\n+        \"se_role\": selinux[1] if lsel > 1 else None,\n+        \"se_type\": selinux[2] if lsel > 2 else None,\n+        \"se_mls\": selinux[3] if lsel > 3 else None,\n+        \"size\": int(size),\n+        \"name\": path,\n+        \"date\": date,\n+    }\n+    if link:\n+        result[\"link\"] = link\n+    return result\n+\n+\n PASS_KEYS = set([\"name\", \"total\"])\n DELAYED_KEYS = [\"entries\", \"files\", \"dirs\", \"specials\"]\n \n@@ -137,6 +178,10 @@\n         files = []\n         specials = []\n         for line in self.body:\n+            # we can't split(None, 5) here b/c rhel 6/7 selinux lines only have\n+            # 4 parts before the path, and the path itself could contain\n+            # spaces. Unfortunately, this means we have to split the line again\n+            # below\n             parts = line.split(None, 4)\n             perms = parts[0]\n             typ = perms[0]\n@@ -145,7 +190,13 @@\n                 \"perms\": perms[1:]\n             }\n             if parts[1][0].isdigit():\n-                rest = parse_non_selinux(parts[1:])\n+                # We have to split the line again to see if this is a RHEL8\n+                # selinux stanza. This assumes that the context section will\n+                # always have at least two pieces separated by ':'.\n+                if \":\" in line.split()[4]:\n+                    rest = parse_rhel8_selinux(parts[1:])\n+                else:\n+                    rest = parse_non_selinux(parts[1:])\n             else:\n                 rest = parse_selinux(parts[1:])\n \n@@ -220,5 +271,6 @@\n             continue\n         entries.append(line)\n     name = name or root\n-    doc[name] = Directory(name, total or len(entries), entries)\n+    total = total if total is not None else len(entries)\n+    doc[name] = Directory(name, total, entries)\n     return doc\n", "issue": "Need to enhance parser \"ls_parser\" to parse the output of  command 'ls -laRZ <dir-name>' in RHEL-8 \nThe parser **ls_parser** needs to be enhanced to support parsing the output of command `ls` with an option `Z` (for selinux context) in a RHEL8 system. In RHEL8, additional fields have been introduced in the output of command `ls -laRZ`. For example:\r\n\r\nRHEL7: \r\n\r\n```\r\n#/bin/ls -laRZ /var/lib/nova/instances\r\n/var/lib/nova/instances:\r\ndrwxr-xr-x. nova nova system_u:object_r:nova_var_lib_t:s0 .\r\ndrwxr-xr-x. nova nova system_u:object_r:nova_var_lib_t:s0 ..\r\ndrwxr-xr-x. root root unconfined_u:object_r:nova_var_lib_t:s0 xxxx-xxxx-xxxx-xxxx\r\n```\r\n\r\nRHEL8: \r\n\r\n```\r\n#/bin/ls -laRZ /var/lib/nova/instances\r\n/var/lib/nova/instances:\r\ntotal 0\r\ndrwxr-xr-x. 3 root root unconfined_u:object_r:var_lib_t:s0 50 Apr  8 16:41 .\r\ndrwxr-xr-x. 3 root root unconfined_u:object_r:var_lib_t:s0 23 Apr  8 16:29 ..\r\ndrwxr-xr-x. 2 root root unconfined_u:object_r:var_lib_t:s0 54 Apr  8 16:41 xxxx-xxxx-xxxx-xxxx\r\n```\n", "before_files": [{"content": "\"\"\"\nThis module contains logic for parsing ls output. It attempts to handle\noutput when selinux is enabled or disabled and also skip \"bad\" lines.\n\"\"\"\nimport six\n\n\ndef parse_path(path):\n    \"\"\"\n    Convert possible symbolic link into a source -> target pair.\n\n    Args:\n        path (str): The path portion of an ls output line.\n\n    Returns:\n        A (path, link) tuple where path is always populated and link is a non\n        empty string if the original path is a symoblic link.\n    \"\"\"\n    path, _, link = path.partition(\" -> \")\n    return path, link\n\n\ndef parse_non_selinux(parts):\n    \"\"\"\n    Parse part of an ls output line that isn't selinux.\n\n    Args:\n        parts (list): A four element list of strings representing the initial\n            parts of an ls line after the permission bits. The parts are link\n            count, owner, group, and everything else.\n\n    Returns:\n        A dict containing links, owner, group, date, and name. If the line\n        represented a device, major and minor numbers are included.  Otherwise,\n        size is included. If the raw name was a symbolic link, link is\n        included.\n    \"\"\"\n    links, owner, group, last = parts\n    result = {\n        \"links\": int(links),\n        \"owner\": owner,\n        \"group\": group,\n    }\n\n    # device numbers only go to 256.\n    # If a comma is in the first four characters, the next two elements are\n    # major and minor device numbers. Otherwise, the next element is the size.\n    if \",\" in last[:4]:\n        major, minor, rest = last.split(None, 2)\n        result[\"major\"] = int(major.rstrip(\",\"))\n        result[\"minor\"] = int(minor)\n    else:\n        size, rest = last.split(None, 1)\n        result[\"size\"] = int(size)\n\n    # The date part is always 12 characters regardless of content.\n    result[\"date\"] = rest[:12]\n\n    # Jump over the date and the following space to get the path part.\n    path, link = parse_path(rest[13:])\n    result[\"name\"] = path\n    if link:\n        result[\"link\"] = link\n\n    return result\n\n\ndef parse_selinux(parts):\n    \"\"\"\n    Parse part of an ls output line that is selinux.\n\n    Args:\n        parts (list): A four element list of strings representing the initial\n            parts of an ls line after the permission bits. The parts are owner\n            group, selinux info, and the path.\n\n    Returns:\n        A dict containing owner, group, se_user, se_role, se_type, se_mls, and\n        name. If the raw name was a symbolic link, link is always included.\n\n    \"\"\"\n\n    owner, group = parts[:2]\n    selinux = parts[2].split(\":\")\n    lsel = len(selinux)\n    path, link = parse_path(parts[-1])\n    result = {\n        \"owner\": owner,\n        \"group\": group,\n        \"se_user\": selinux[0],\n        \"se_role\": selinux[1] if lsel > 1 else None,\n        \"se_type\": selinux[2] if lsel > 2 else None,\n        \"se_mls\": selinux[3] if lsel > 3 else None,\n        \"name\": path\n    }\n    if link:\n        result[\"link\"] = link\n    return result\n\n\nPASS_KEYS = set([\"name\", \"total\"])\nDELAYED_KEYS = [\"entries\", \"files\", \"dirs\", \"specials\"]\n\n\nclass Directory(dict):\n    def __init__(self, name, total, body):\n        data = dict.fromkeys(DELAYED_KEYS)\n        data[\"name\"] = name\n        data[\"total\"] = total\n        self.body = body\n        self.loaded = False\n        super(Directory, self).__init__(data)\n\n    def iteritems(self):\n        if not self.loaded:\n            self._load()\n        return six.iteritems(super(Directory, self))\n\n    def items(self):\n        if not self.loaded:\n            self._load()\n        return super(Directory, self).items()\n\n    def values(self):\n        if not self.loaded:\n            self._load()\n        return super(Directory, self).values()\n\n    def get(self, key, default=None):\n        if not self.loaded:\n            self._load()\n        return super(Directory, self).get(key, default)\n\n    def _load(self):\n        dirs = []\n        ents = {}\n        files = []\n        specials = []\n        for line in self.body:\n            parts = line.split(None, 4)\n            perms = parts[0]\n            typ = perms[0]\n            entry = {\n                \"type\": typ,\n                \"perms\": perms[1:]\n            }\n            if parts[1][0].isdigit():\n                rest = parse_non_selinux(parts[1:])\n            else:\n                rest = parse_selinux(parts[1:])\n\n            # Update our entry and put it into the correct buckets\n            # based on its type.\n            entry.update(rest)\n            entry[\"raw_entry\"] = line\n            entry[\"dir\"] = self[\"name\"]\n            nm = entry[\"name\"]\n            ents[nm] = entry\n            if typ not in \"bcd\":\n                files.append(nm)\n            elif typ == \"d\":\n                dirs.append(nm)\n            elif typ in \"bc\":\n                specials.append(nm)\n\n        self.update({\"entries\": ents,\n                     \"files\": files,\n                     \"dirs\": dirs,\n                     \"specials\": specials})\n\n        self.loaded = True\n        del self.body\n\n    def __getitem__(self, key):\n        if self.loaded or key in PASS_KEYS:\n            return super(Directory, self).__getitem__(key)\n        self._load()\n        return super(Directory, self).__getitem__(key)\n\n\ndef parse(lines, root=None):\n    \"\"\"\n    Parses a list of lines from ls into dictionaries representing their\n    components.\n\n    Args:\n        lines (list): A list of lines generated by ls.\n        root (str): The directory name to be used for ls output stanzas that\n            don't have a name.\n\n    Returns:\n        A dictionary representing the ls output. It's keyed by the path\n        containing each ls stanza.\n    \"\"\"\n    doc = {}\n    entries = []\n    name = None\n    total = None\n    for line in lines:\n        line = line.strip()\n        if not line:\n            continue\n        if line and line[0] == \"/\" and line[-1] == \":\":\n            if name is None:\n                name = line[:-1]\n                if entries:\n                    d = Directory(name, total or len(entries), entries)\n                    doc[root] = d\n                    total = None\n                    entries = []\n            else:\n                d = Directory(name, total or len(entries), entries)\n                doc[name or root] = d\n                total = None\n                entries = []\n                name = line[:-1]\n            continue\n        if line.startswith(\"total\"):\n            total = int(line.split(None, 1)[1])\n            continue\n        entries.append(line)\n    name = name or root\n    doc[name] = Directory(name, total or len(entries), entries)\n    return doc\n", "path": "insights/core/ls_parser.py"}], "after_files": [{"content": "\"\"\"\nThis module contains logic for parsing ls output. It attempts to handle\noutput when selinux is enabled or disabled and also skip \"bad\" lines.\n\"\"\"\nimport six\n\n\ndef parse_path(path):\n    \"\"\"\n    Convert possible symbolic link into a source -> target pair.\n\n    Args:\n        path (str): The path portion of an ls output line.\n\n    Returns:\n        A (path, link) tuple where path is always populated and link is a non\n        empty string if the original path is a symoblic link.\n    \"\"\"\n    path, _, link = path.partition(\" -> \")\n    return path, link\n\n\ndef parse_non_selinux(parts):\n    \"\"\"\n    Parse part of an ls output line that isn't selinux.\n\n    Args:\n        parts (list): A four element list of strings representing the initial\n            parts of an ls line after the permission bits. The parts are link\n            count, owner, group, and everything else.\n\n    Returns:\n        A dict containing links, owner, group, date, and name. If the line\n        represented a device, major and minor numbers are included.  Otherwise,\n        size is included. If the raw name was a symbolic link, link is\n        included.\n    \"\"\"\n    links, owner, group, last = parts\n    result = {\n        \"links\": int(links),\n        \"owner\": owner,\n        \"group\": group,\n    }\n\n    # device numbers only go to 256.\n    # If a comma is in the first four characters, the next two elements are\n    # major and minor device numbers. Otherwise, the next element is the size.\n    if \",\" in last[:4]:\n        major, minor, rest = last.split(None, 2)\n        result[\"major\"] = int(major.rstrip(\",\"))\n        result[\"minor\"] = int(minor)\n    else:\n        size, rest = last.split(None, 1)\n        result[\"size\"] = int(size)\n\n    # The date part is always 12 characters regardless of content.\n    result[\"date\"] = rest[:12]\n\n    # Jump over the date and the following space to get the path part.\n    path, link = parse_path(rest[13:])\n    result[\"name\"] = path\n    if link:\n        result[\"link\"] = link\n\n    return result\n\n\ndef parse_selinux(parts):\n    \"\"\"\n    Parse part of an ls output line that is selinux.\n\n    Args:\n        parts (list): A four element list of strings representing the initial\n            parts of an ls line after the permission bits. The parts are owner\n            group, selinux info, and the path.\n\n    Returns:\n        A dict containing owner, group, se_user, se_role, se_type, se_mls, and\n        name. If the raw name was a symbolic link, link is also included.\n\n    \"\"\"\n\n    owner, group = parts[:2]\n    selinux = parts[2].split(\":\")\n    lsel = len(selinux)\n    path, link = parse_path(parts[-1])\n    result = {\n        \"owner\": owner,\n        \"group\": group,\n        \"se_user\": selinux[0],\n        \"se_role\": selinux[1] if lsel > 1 else None,\n        \"se_type\": selinux[2] if lsel > 2 else None,\n        \"se_mls\": selinux[3] if lsel > 3 else None,\n        \"name\": path\n    }\n    if link:\n        result[\"link\"] = link\n    return result\n\n\ndef parse_rhel8_selinux(parts):\n    \"\"\"\n    Parse part of an ls output line that is selinux on RHEL8.\n\n    Args:\n        parts (list): A four element list of strings representing the initial\n            parts of an ls line after the permission bits. The parts are link\n            count, owner, group, and everything else\n\n    Returns:\n        A dict containing links, owner, group, se_user, se_role, se_type,\n        se_mls, size, date, and name. If the raw name was a symbolic link,\n        link is also included.\n\n    \"\"\"\n\n    links, owner, group, last = parts\n\n    selinux = parts[3].split(\":\")\n    lsel = len(selinux)\n    selinux, size, last = parts[-1].split(None, 2)\n    selinux = selinux.split(\":\")\n    date = last[:12]\n    path, link = parse_path(last[13:])\n    result = {\n        \"links\": int(links),\n        \"owner\": owner,\n        \"group\": group,\n        \"se_user\": selinux[0],\n        \"se_role\": selinux[1] if lsel > 1 else None,\n        \"se_type\": selinux[2] if lsel > 2 else None,\n        \"se_mls\": selinux[3] if lsel > 3 else None,\n        \"size\": int(size),\n        \"name\": path,\n        \"date\": date,\n    }\n    if link:\n        result[\"link\"] = link\n    return result\n\n\nPASS_KEYS = set([\"name\", \"total\"])\nDELAYED_KEYS = [\"entries\", \"files\", \"dirs\", \"specials\"]\n\n\nclass Directory(dict):\n    def __init__(self, name, total, body):\n        data = dict.fromkeys(DELAYED_KEYS)\n        data[\"name\"] = name\n        data[\"total\"] = total\n        self.body = body\n        self.loaded = False\n        super(Directory, self).__init__(data)\n\n    def iteritems(self):\n        if not self.loaded:\n            self._load()\n        return six.iteritems(super(Directory, self))\n\n    def items(self):\n        if not self.loaded:\n            self._load()\n        return super(Directory, self).items()\n\n    def values(self):\n        if not self.loaded:\n            self._load()\n        return super(Directory, self).values()\n\n    def get(self, key, default=None):\n        if not self.loaded:\n            self._load()\n        return super(Directory, self).get(key, default)\n\n    def _load(self):\n        dirs = []\n        ents = {}\n        files = []\n        specials = []\n        for line in self.body:\n            # we can't split(None, 5) here b/c rhel 6/7 selinux lines only have\n            # 4 parts before the path, and the path itself could contain\n            # spaces. Unfortunately, this means we have to split the line again\n            # below\n            parts = line.split(None, 4)\n            perms = parts[0]\n            typ = perms[0]\n            entry = {\n                \"type\": typ,\n                \"perms\": perms[1:]\n            }\n            if parts[1][0].isdigit():\n                # We have to split the line again to see if this is a RHEL8\n                # selinux stanza. This assumes that the context section will\n                # always have at least two pieces separated by ':'.\n                if \":\" in line.split()[4]:\n                    rest = parse_rhel8_selinux(parts[1:])\n                else:\n                    rest = parse_non_selinux(parts[1:])\n            else:\n                rest = parse_selinux(parts[1:])\n\n            # Update our entry and put it into the correct buckets\n            # based on its type.\n            entry.update(rest)\n            entry[\"raw_entry\"] = line\n            entry[\"dir\"] = self[\"name\"]\n            nm = entry[\"name\"]\n            ents[nm] = entry\n            if typ not in \"bcd\":\n                files.append(nm)\n            elif typ == \"d\":\n                dirs.append(nm)\n            elif typ in \"bc\":\n                specials.append(nm)\n\n        self.update({\"entries\": ents,\n                     \"files\": files,\n                     \"dirs\": dirs,\n                     \"specials\": specials})\n\n        self.loaded = True\n        del self.body\n\n    def __getitem__(self, key):\n        if self.loaded or key in PASS_KEYS:\n            return super(Directory, self).__getitem__(key)\n        self._load()\n        return super(Directory, self).__getitem__(key)\n\n\ndef parse(lines, root=None):\n    \"\"\"\n    Parses a list of lines from ls into dictionaries representing their\n    components.\n\n    Args:\n        lines (list): A list of lines generated by ls.\n        root (str): The directory name to be used for ls output stanzas that\n            don't have a name.\n\n    Returns:\n        A dictionary representing the ls output. It's keyed by the path\n        containing each ls stanza.\n    \"\"\"\n    doc = {}\n    entries = []\n    name = None\n    total = None\n    for line in lines:\n        line = line.strip()\n        if not line:\n            continue\n        if line and line[0] == \"/\" and line[-1] == \":\":\n            if name is None:\n                name = line[:-1]\n                if entries:\n                    d = Directory(name, total or len(entries), entries)\n                    doc[root] = d\n                    total = None\n                    entries = []\n            else:\n                d = Directory(name, total or len(entries), entries)\n                doc[name or root] = d\n                total = None\n                entries = []\n                name = line[:-1]\n            continue\n        if line.startswith(\"total\"):\n            total = int(line.split(None, 1)[1])\n            continue\n        entries.append(line)\n    name = name or root\n    total = total if total is not None else len(entries)\n    doc[name] = Directory(name, total, entries)\n    return doc\n", "path": "insights/core/ls_parser.py"}]}