1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6
7import os
8
9def sort_shadowutils_file(filename, mapping):
10    """
11    Sorts a passwd or group file based on the numeric ID in the third column.
12    If a mapping is given, the name from the first column is mapped via that
13    dictionary instead (necessary for /etc/shadow and /etc/gshadow). If not,
14    a new mapping is created on the fly and returned.
15    """
16
17    new_mapping = {}
18    with open(filename, 'rb+') as f:
19        lines = f.readlines()
20        # No explicit error checking for the sake of simplicity. /etc
21        # files are assumed to be well-formed, causing exceptions if
22        # not.
23        for line in lines:
24            entries = line.split(b':')
25            name = entries[0]
26            if mapping is None:
27                id = int(entries[2])
28            else:
29                id = mapping[name]
30            new_mapping[name] = id
31        # Sort by numeric id first, with entire line as secondary key
32        # (just in case that there is more than one entry for the same id).
33        lines.sort(key=lambda line: (new_mapping[line.split(b':')[0]], line))
34        # We overwrite the entire file, i.e. no truncate() necessary.
35        f.seek(0)
36        f.write(b''.join(lines))
37
38    return new_mapping
39
40def sort_shadowutils_files(sysconfdir):
41    """
42    Sorts shadow-utils 'passwd' and 'group' files in a rootfs' /etc directory
43    by ID.
44    """
45
46    for main, shadow in (('passwd', 'shadow'),
47                         ('group', 'gshadow')):
48        filename = os.path.join(sysconfdir, main)
49        if os.path.exists(filename):
50            mapping = sort_shadowutils_file(filename, None)
51            filename = os.path.join(sysconfdir, shadow)
52            if os.path.exists(filename):
53                 sort_shadowutils_file(filename, mapping)
54
55def remove_shadowutils_backup_file(filename):
56    """
57    Remove shadow-utils backup file for files like /etc/passwd.
58    """
59
60    backup_filename = filename + '-'
61    if os.path.exists(backup_filename):
62        os.unlink(backup_filename)
63
64def remove_shadowutils_backup_files(sysconfdir):
65    """
66    Remove shadow-utils backup files in a rootfs /etc directory. They are not
67    needed in the initial root filesystem and sorting them can be inconsistent
68    (YOCTO #11043).
69    """
70
71    for filename in (
72            'group',
73            'gshadow',
74            'passwd',
75            'shadow',
76            'subgid',
77            'subuid',
78        ):
79        filepath = os.path.join(sysconfdir, filename)
80        remove_shadowutils_backup_file(filepath)
81
82def tidy_shadowutils_files(sysconfdir):
83    """
84    Tidy up shadow-utils files.
85    """
86
87    remove_shadowutils_backup_files(sysconfdir)
88    sort_shadowutils_files(sysconfdir)
89
90    return True
91