Commit a35a529d authored by jan.koester's avatar jan.koester
Browse files

getfacl implmented

parent c196d2f6
Loading
Loading
Loading
Loading
+2 −1
Changes for commands/filesystem/CMakeLists.txt: 2 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@ target_sources(fresh PRIVATE
  ${CMAKE_CURRENT_SOURCE_DIR}/dirname.cpp
  ${CMAKE_CURRENT_SOURCE_DIR}/chmod.cpp
  ${CMAKE_CURRENT_SOURCE_DIR}/chown.cpp
  ${CMAKE_CURRENT_SOURCE_DIR}/getfacl.cpp
  ${CMAKE_CURRENT_SOURCE_DIR}/find.cpp
  ${CMAKE_CURRENT_SOURCE_DIR}/install.cpp
  ${CMAKE_CURRENT_SOURCE_DIR}/which.cpp
@@ -24,4 +25,4 @@ target_sources(fresh PRIVATE
)

set(FRESH_APPLETS ${FRESH_APPLETS} cat ls touch mkdir rm ln cp mv dd rmdir pwd
    basename dirname chmod chown find install which readlink realpath mktemp stat du df mkfifo truncate sync PARENT_SCOPE)
    basename dirname chmod chown getfacl find install which readlink realpath mktemp stat du df mkfifo truncate sync PARENT_SCOPE)
+220 −0
Changes for commands/filesystem/getfacl.cpp: 220 added lines, 0 removed lines.
Original line number Diff line number Diff line
/*******************************************************************************
Copyright (c) 2026, Jan Koester jan.koester@gmx.net
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the <organization> nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/

#include <iostream>
#include <string>
#include <vector>
#include <cstring>
#include <cerrno>
#include <cstdint>

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/xattr.h>
#include <unistd.h>

#include "shell.h"
#include "ids.h"

namespace fresh { namespace cmd {

    // "getfacl [-a|-d] [-c] [-n] FILE...": a file's POSIX ACL in the text
    // form of acl(5), from system.posix_acl_access / _default. The xattrs
    // come in version 2 (classic 32-bit ids) or, when an entry is a UUID or
    // a GUID (FenrirOS uid_r), version 3: e_tag, e_perm, e_type (UID_R_*)
    // and 16 bytes of id. Without an access ACL the mode bits are shown as
    // the minimal ACL, like the real utility.
    class GetfaclCommand : public ShellCommand {
    public:
        GetfaclCommand():ShellCommand("getfacl"){}

        enum { ACL_USER_OBJ=0x01, ACL_USER=0x02, ACL_GROUP_OBJ=0x04,
               ACL_GROUP=0x08, ACL_MASK=0x10, ACL_OTHER=0x20 };

        struct Entry {
            uint16_t tag=0,perm=0;
            uid_r_t id=uid_r_none();
        };

        static uint16_t le16(const unsigned char *p){ return (uint16_t)(p[0] | p[1]<<8); }
        static uint32_t le32(const unsigned char *p){
            return (uint32_t)p[0] | (uint32_t)p[1]<<8 | (uint32_t)p[2]<<16 | (uint32_t)p[3]<<24;
        }

        // false for a malformed xattr
        static bool parse(const std::vector<unsigned char> &b,std::vector<Entry> &out){
            if(b.size()<4) return false;
            uint32_t version=le32(b.data());
            size_t each=version==2 ? 8 : version==3 ? 24 : 0;
            if(!each || (b.size()-4)%each) return false;
            for(size_t off=4; off<b.size(); off+=each){
                const unsigned char *p=b.data()+off;
                Entry e;
                e.tag=le16(p);
                e.perm=le16(p+2);
                if(version==2){
                    e.id=uid_r_legacy(le32(p+4));
                } else {
                    uint32_t type=le32(p+4);
                    memset(&e.id,0,sizeof(e.id));
                    e.id.type=type;
                    if(type==UID_R_LEGACY) e.id.legacy=le32(p+8);
                    else if(type==UID_R_UUID || type==UID_R_GUID) memcpy(e.id.uuid,p+8,16);
                    else if(type!=UID_R_NONE) return false;
                }
                out.push_back(e);
            }
            return true;
        }

        // 1 found, 0 none, -1 error (errno)
        static int readAcl(const std::string &path,const char *name,std::vector<Entry> &out){
            std::vector<unsigned char> buf(1024);
            for(;;){
                ssize_t n=getxattr(path.c_str(),name,buf.data(),buf.size());
                if(n>=0){
                    buf.resize((size_t)n);
                    if(!parse(buf,out)){ errno=EINVAL; return -1; }
                    return 1;
                }
                if(errno==ERANGE && buf.size()<(1u<<20)){ buf.resize(buf.size()*4); continue; }
                if(errno==ENODATA || errno==ENOTSUP || errno==EOPNOTSUPP) return 0;
                return -1;
            }
        }

        static std::string perms(uint16_t p){
            std::string s="---";
            if(p&4) s[0]='r';
            if(p&2) s[1]='w';
            if(p&1) s[2]='x';
            return s;
        }

        static std::string qualifier(const Entry &e,bool numeric){
            // -1: no mapping in this user namespace -- the number, so the
            // line can't be taken for user:: or group::
            if(numeric || (e.id.type==UID_R_LEGACY && e.id.legacy==0xffffffffu)) return ids::str(e.id);
            return e.tag==ACL_USER ? ids::userName(e.id) : ids::groupName(e.id);
        }

        static void print(const std::vector<Entry> &acl,const char *prefix,bool numeric,uint16_t mask,bool haveMask){
            for(const Entry &e : acl){
                std::string line=prefix;
                switch(e.tag){
                case ACL_USER_OBJ:  line+="user::"; break;
                case ACL_USER:      line+="user:"+qualifier(e,numeric)+":"; break;
                case ACL_GROUP_OBJ: line+="group::"; break;
                case ACL_GROUP:     line+="group:"+qualifier(e,numeric)+":"; break;
                case ACL_MASK:      line+="mask::"; break;
                case ACL_OTHER:     line+="other::"; break;
                default:            line+="?:"; break;
                }
                line+=perms(e.perm);
                // what the mask leaves of a named or group entry
                bool masked=haveMask && (e.tag==ACL_USER || e.tag==ACL_GROUP_OBJ || e.tag==ACL_GROUP);
                if(masked && (e.perm&~mask&7))
                    line+="\t#effective:"+perms(e.perm&mask);
                std::cout << line << "\n";
            }
        }

        static bool show(const std::string &path,bool access,bool dflt,bool header,bool numeric,bool first){
            struct stat st;
            if(stat(path.c_str(),&st)!=0){
                std::cerr << "getfacl: " << path << ": " << strerror(errno) << std::endl;
                return false;
            }
            std::vector<Entry> acl,def;
            int ra=access ? readAcl(path,"system.posix_acl_access",acl) : 0;
            int rd=dflt && S_ISDIR(st.st_mode) ? readAcl(path,"system.posix_acl_default",def) : 0;
            if(ra<0 || rd<0){
                std::cerr << "getfacl: " << path << ": " << strerror(errno) << std::endl;
                return false;
            }
            if(access && ra==0){
                // no ACL: the mode bits are the minimal one
                Entry u,g,o;
                u.tag=ACL_USER_OBJ;  u.perm=(st.st_mode>>6)&7;
                g.tag=ACL_GROUP_OBJ; g.perm=(st.st_mode>>3)&7;
                o.tag=ACL_OTHER;     o.perm=st.st_mode&7;
                acl={u,g,o};
            }
            if(!first) std::cout << "\n";
            if(header){
                uid_r_t uid; gid_r_t gid;
                ids::fileIds(AT_FDCWD,path.c_str(),0,st,uid,gid);
                std::cout << "# file: " << (path[0]=='/' ? path.substr(1) : path) << "\n";
                std::cout << "# owner: " << (numeric ? ids::str(uid) : ids::userName(uid)) << "\n";
                std::cout << "# group: " << (numeric ? ids::str(gid) : ids::groupName(gid)) << "\n";
            }
            auto maskOf=[](const std::vector<Entry> &a,uint16_t &m){
                for(const Entry &e : a) if(e.tag==ACL_MASK){ m=e.perm; return true; }
                return false;
            };
            uint16_t m=7;
            if(access){ bool hm=maskOf(acl,m); print(acl,"",numeric,m,hm); }
            if(!def.empty()){ m=7; bool hm=maskOf(def,m); print(def,access ? "default:" : "",numeric,m,hm); }
            return true;
        }

        int runCommand(int argc,const char **argv){
            bool access=true,dflt=true,header=true,numeric=false;
            int i=1;
            for(; i<argc && argv[i][0]=='-' && argv[i][1]; ++i){
                std::string a=argv[i];
                if(a=="--"){ ++i; break; }
                if(a=="--access"){ dflt=false; continue; }
                if(a=="--default"){ access=false; continue; }
                if(a=="--omit-header"){ header=false; continue; }
                if(a=="--numeric"){ numeric=true; continue; }
                for(size_t k=1; k<a.size(); ++k){
                    switch(a[k]){
                    case 'a': dflt=false; break;
                    case 'd': access=false; break;
                    case 'c': header=false; break;
                    case 'n': numeric=true; break;
                    default:
                        std::cerr << "getfacl: usage: getfacl [-a|-d] [-c] [-n] FILE..." << std::endl;
                        return 1;
                    }
                }
            }
            if(i>=argc){
                std::cerr << "getfacl: usage: getfacl [-a|-d] [-c] [-n] FILE..." << std::endl;
                return 1;
            }
            bool ok=true;
            for(bool first=true; i<argc; ++i, first=false)
                if(!show(argv[i],access,dflt,header,numeric,first)) ok=false;
            return ok ? 0 : 1;
        }
    };

    FRESH_REGISTER_COMMAND(GetfaclCommand);

}}