The brief summary: all the system calls deal with extended attributes, not acls; these system calls result in getxattr and setxattr vfs ops, and it is up to the filesystem code to check itself whether a given xattr is actually an acl; it uses generic acl stuff in fs/xattr_acl.c as necessary to help with this. All acl's (and xattr's) get set by system calls sys_setxattr, sys_lsetxattr, sys_fsetxattr, for files/directories, symlinks, and file descriptors, respectively. All get turned into setxattr() (which is passed a dentry). This copies the xattr to a buffer (of maximum size 64k) and calls the setxattr inode op, with the i_sem down. The name of the xattr is passed in a null-terminated string (max. length 255 without null). The setxattr field for ext2 files, links, and directories is set to fs/ext2/xattr.c:ext2_setxattr(). ext2 uses ext2_xattr_resolv_name() to find the first struct ext2_xattr_handler in its list which has a prefix that matches completely the beginning of the xattr's name. struct ext2_xattr_handler { char *prefix; size_t (*list)(char *list, struct inode *inode, const char *name, int name_len); int (*get)(struct inode *inode, const char *name, void *buffer, size_t size); int (*set)(struct inode *inode, const char *name, const void *buffer, size_t size, int flags); } It then calls the "set" method from that struct. The name it calls it with is actually the original xattr name minus the prefix. Handlers are registered using ext2_xattr_register; there are 6 of them: EXT2_XATTR_INDEX_POSIX_ACL_ACCESS: ordinary acls EXT2_XATTR_INDEX_POSIX_ACL_DEFAULT: default acls on directories. EXT2_XATTR_INDEX_USER EXT2_XATTR_INDEX_TRUSTED EXT2_XATTR_INDEX_SECURITY EXT2_XATTR_INDEX_USER The set method for ordinary acls is ext2_xattr_set_acl_access(), and for default acls is ext2_xattr_set_acl_default(). Both call ext2_xattr_set_acl(), with a "type" flag to distinguish between the two cases. This calls fs/xattr_acl.c:posix_acl_from_xattr() to parse the extended attribute and create a new struct posix_acl (allocating memory with kmalloc(,GFP_KERNEL)), calls ext2_set_acl(), then releases the posix acl. ext2_set_acl() returns -EACCES if we attempt to set a default acl on a directory (and if the provided ACL is non-null). Returns -EOPNOTSUPP on symlinks. Returns 0 if acl's not supported. Returns -EINVAL if there are more than EXT2_ACL_MAX_ENTRIES in the acl to be set. In the case of a regular (non-default) acl, it also makes a best effort to convert the acl to a mode, writes that to the inode, and marks the inode dirty. Next it converts the struct posix_acl to an on-disk representation. Then it calls ext2_xattr_set(), which I don't understand but which seems to do the job of actually writing the blocks that need to be written. Finally, ext2_iset_acl() is called to modify the in-memory inode, which uses EXT2_ACL_NOT_CACHE = ((void *)-1) to indicate whether an acl is there. It assigns to the i_acl (or i_default_acl) field, releasing the old acl if necessary. It does this under the i->lock spinlock, in case of a concurrent ext2_iget_acl() (the corresponding get_acl code doesn't take the i_sem). How do they do the cached lookup in the get_acl path? Most of the work is done by ext2_get_acl(), which first does an ext2_iget_acl() to check if it's already cached in the inode, and then uses ext2_xattr_get() to read the acl from disk if necessary, using ext_iset_acl() to cache it afterwards.