1#! /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()) 216