1# cython: language_level=3
2
3import os
4
5from ..helpers import user2uid, group2gid
6from ..helpers import safe_decode, safe_encode
7from .posix import swidth
8
9API_VERSION = '1.1_04'
10
11cdef extern from "sys/acl.h":
12    ctypedef struct _acl_t:
13        pass
14    ctypedef _acl_t *acl_t
15
16    int acl_free(void *obj)
17    acl_t acl_get_link_np(const char *path, int type)
18    int acl_set_link_np(const char *path, int type, acl_t acl)
19    acl_t acl_from_text(const char *buf)
20    char *acl_to_text(acl_t acl, ssize_t *len_p)
21    int ACL_TYPE_EXTENDED
22
23
24def _remove_numeric_id_if_possible(acl):
25    """Replace the user/group field with the local uid/gid if possible
26    """
27    entries = []
28    for entry in safe_decode(acl).split('\n'):
29        if entry:
30            fields = entry.split(':')
31            if fields[0] == 'user':
32                if user2uid(fields[2]) is not None:
33                    fields[1] = fields[3] = ''
34            elif fields[0] == 'group':
35                if group2gid(fields[2]) is not None:
36                    fields[1] = fields[3] = ''
37            entries.append(':'.join(fields))
38    return safe_encode('\n'.join(entries))
39
40
41def _remove_non_numeric_identifier(acl):
42    """Remove user and group names from the acl
43    """
44    entries = []
45    for entry in safe_decode(acl).split('\n'):
46        if entry:
47            fields = entry.split(':')
48            if fields[0] in ('user', 'group'):
49                fields[2] = ''
50                entries.append(':'.join(fields))
51            else:
52                entries.append(entry)
53    return safe_encode('\n'.join(entries))
54
55
56def acl_get(path, item, st, numeric_owner=False):
57    cdef acl_t acl = NULL
58    cdef char *text = NULL
59    try:
60        acl = acl_get_link_np(<bytes>os.fsencode(path), ACL_TYPE_EXTENDED)
61        if acl == NULL:
62            return
63        text = acl_to_text(acl, NULL)
64        if text == NULL:
65            return
66        if numeric_owner:
67            item['acl_extended'] = _remove_non_numeric_identifier(text)
68        else:
69            item['acl_extended'] = text
70    finally:
71        acl_free(text)
72        acl_free(acl)
73
74
75def acl_set(path, item, numeric_owner=False):
76    cdef acl_t acl = NULL
77    acl_text = item.get('acl_extended')
78    if acl_text is not None:
79        try:
80            if numeric_owner:
81                acl = acl_from_text(acl_text)
82            else:
83                acl = acl_from_text(<bytes>_remove_numeric_id_if_possible(acl_text))
84            if acl == NULL:
85                return
86            if acl_set_link_np(<bytes>os.fsencode(path), ACL_TYPE_EXTENDED, acl):
87                return
88        finally:
89            acl_free(acl)
90