blob: 0f41b275cf6d8d12a6ec0e2c844a444e52499f59 [file] [log] [blame]
Andrew Geissler5082cc72023-09-11 08:41:39 -04001#! /usr/bin/env python3
2#
3# Copyright 2023 by Garmin Ltd. or its subsidiaries
4#
5# SPDX-License-Identifier: MIT
6
7
8import sys
9import ctypes
10import os
11import errno
12import pwd
13import grp
14
15libacl = ctypes.CDLL("libacl.so.1", use_errno=True)
16
17
18ACL_TYPE_ACCESS = 0x8000
19ACL_TYPE_DEFAULT = 0x4000
20
21ACL_FIRST_ENTRY = 0
22ACL_NEXT_ENTRY = 1
23
24ACL_UNDEFINED_TAG = 0x00
25ACL_USER_OBJ = 0x01
26ACL_USER = 0x02
27ACL_GROUP_OBJ = 0x04
28ACL_GROUP = 0x08
29ACL_MASK = 0x10
30ACL_OTHER = 0x20
31
32ACL_READ = 0x04
33ACL_WRITE = 0x02
34ACL_EXECUTE = 0x01
35
36acl_t = ctypes.c_void_p
37acl_entry_t = ctypes.c_void_p
38acl_permset_t = ctypes.c_void_p
39acl_perm_t = ctypes.c_uint
40
41acl_tag_t = ctypes.c_int
42
43libacl.acl_free.argtypes = [acl_t]
44
45
46def acl_free(acl):
47 libacl.acl_free(acl)
48
49
50libacl.acl_get_file.restype = acl_t
51libacl.acl_get_file.argtypes = [ctypes.c_char_p, ctypes.c_uint]
52
53
54def acl_get_file(path, typ):
55 acl = libacl.acl_get_file(os.fsencode(path), typ)
56 if acl is None:
57 err = ctypes.get_errno()
58 raise OSError(err, os.strerror(err), str(path))
59
60 return acl
61
62
63libacl.acl_get_entry.argtypes = [acl_t, ctypes.c_int, ctypes.c_void_p]
64
65
66def acl_get_entry(acl, entry_id):
67 entry = acl_entry_t()
68 ret = libacl.acl_get_entry(acl, entry_id, ctypes.byref(entry))
69 if ret < 0:
70 err = ctypes.get_errno()
71 raise OSError(err, os.strerror(err))
72
73 if ret == 0:
74 return None
75
76 return entry
77
78
79libacl.acl_get_tag_type.argtypes = [acl_entry_t, ctypes.c_void_p]
80
81
82def acl_get_tag_type(entry_d):
83 tag = acl_tag_t()
84 ret = libacl.acl_get_tag_type(entry_d, ctypes.byref(tag))
85 if ret < 0:
86 err = ctypes.get_errno()
87 raise OSError(err, os.strerror(err))
88 return tag.value
89
90
91libacl.acl_get_qualifier.restype = ctypes.c_void_p
92libacl.acl_get_qualifier.argtypes = [acl_entry_t]
93
94
95def acl_get_qualifier(entry_d):
96 ret = libacl.acl_get_qualifier(entry_d)
97 if ret is None:
98 err = ctypes.get_errno()
99 raise OSError(err, os.strerror(err))
100 return ctypes.c_void_p(ret)
101
102
103libacl.acl_get_permset.argtypes = [acl_entry_t, ctypes.c_void_p]
104
105
106def acl_get_permset(entry_d):
107 permset = acl_permset_t()
108 ret = libacl.acl_get_permset(entry_d, ctypes.byref(permset))
109 if ret < 0:
110 err = ctypes.get_errno()
111 raise OSError(err, os.strerror(err))
112
113 return permset
114
115
116libacl.acl_get_perm.argtypes = [acl_permset_t, acl_perm_t]
117
118
119def acl_get_perm(permset_d, perm):
120 ret = libacl.acl_get_perm(permset_d, perm)
121 if ret < 0:
122 err = ctypes.get_errno()
123 raise OSError(err, os.strerror(err))
124 return bool(ret)
125
126
127class Entry(object):
128 def __init__(self, tag, qualifier, mode):
129 self.tag = tag
130 self.qualifier = qualifier
131 self.mode = mode
132
133 def __str__(self):
134 typ = ""
135 qual = ""
136 if self.tag == ACL_USER:
137 typ = "user"
138 qual = pwd.getpwuid(self.qualifier).pw_name
139 elif self.tag == ACL_GROUP:
140 typ = "group"
141 qual = grp.getgrgid(self.qualifier).gr_name
142 elif self.tag == ACL_USER_OBJ:
143 typ = "user"
144 elif self.tag == ACL_GROUP_OBJ:
145 typ = "group"
146 elif self.tag == ACL_MASK:
147 typ = "mask"
148 elif self.tag == ACL_OTHER:
149 typ = "other"
150
151 r = "r" if self.mode & ACL_READ else "-"
152 w = "w" if self.mode & ACL_WRITE else "-"
153 x = "x" if self.mode & ACL_EXECUTE else "-"
154
155 return f"{typ}:{qual}:{r}{w}{x}"
156
157
158class ACL(object):
159 def __init__(self, acl):
160 self.acl = acl
161
162 def __del__(self):
163 acl_free(self.acl)
164
165 def entries(self):
166 entry_id = ACL_FIRST_ENTRY
167 while True:
168 entry = acl_get_entry(self.acl, entry_id)
169 if entry is None:
170 break
171
172 permset = acl_get_permset(entry)
173
174 mode = 0
175 for m in (ACL_READ, ACL_WRITE, ACL_EXECUTE):
176 if acl_get_perm(permset, m):
177 mode |= m
178
179 qualifier = None
180 tag = acl_get_tag_type(entry)
181
182 if tag == ACL_USER or tag == ACL_GROUP:
183 qual = acl_get_qualifier(entry)
184 qualifier = ctypes.cast(qual, ctypes.POINTER(ctypes.c_int))[0]
185
186 yield Entry(tag, qualifier, mode)
187
188 entry_id = ACL_NEXT_ENTRY
189
190 @classmethod
191 def from_path(cls, path, typ):
192 acl = acl_get_file(path, typ)
193 return cls(acl)
194
195
196def main():
197 import argparse
198 import pwd
199 import grp
200 from pathlib import Path
201
202 parser = argparse.ArgumentParser()
203 parser.add_argument("path", help="File Path", type=Path)
204
205 args = parser.parse_args()
206
207 acl = ACL.from_path(args.path, ACL_TYPE_ACCESS)
208 for entry in acl.entries():
209 print(str(entry))
210
211 return 0
212
213
214if __name__ == "__main__":
215 sys.exit(main())