Commit 2a8c08f3 authored by jan.koester's avatar jan.koester
Browse files

extended shell utils

parent 8fa3c245
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
@@ -20,7 +20,8 @@ target_sources(fresh PRIVATE
  ${CMAKE_CURRENT_SOURCE_DIR}/readlink.cpp
  ${CMAKE_CURRENT_SOURCE_DIR}/realpath.cpp
  ${CMAKE_CURRENT_SOURCE_DIR}/mktemp.cpp
  ${CMAKE_CURRENT_SOURCE_DIR}/fsutils.cpp
)

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 PARENT_SCOPE)
    basename dirname chmod chown find install which readlink realpath mktemp stat du df mkfifo truncate sync PARENT_SCOPE)
+91 −137
Changes for commands/filesystem/chmod.cpp: 91 added lines, 137 removed lines.
Original line number Diff line number Diff line
@@ -34,177 +34,131 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <vector>
#include <cstdio>

#include "shell.h"
#include "modeutil.h"

namespace fresh { namespace cmd {

    // "chmod [-R] MODE FILE...": MODE is either a plain octal number
    // (matching the overwhelming majority of real usage, e.g. spec files'
    // "chmod 0755 ...") or a symbolic clause list "[ugoa]*[+-=][rwxXst]+"
    // (","-separated, e.g. "u+x,go-w") applied against each target's
    // CURRENT mode in turn -- no full parity with GNU chmod's grammar
    // (no numeric "+"/"-" deltas, no multiple ops chained after one
    // who-spec via ":"), but covers everything scripts actually write.
    // chmod [-R] [-f] [-v] [-c] (MODE | --reference=RFILE) FILE...
    // MODE is octal or a GNU symbolic list (see modeutil.h). With -R,
    // symlinks found below the operands are skipped, as GNU's default -P.
    class ChmodCommand : public ShellCommand {
    public:
        ChmodCommand():ShellCommand("chmod"){}

        static bool isOctal(const std::string &s){
            if(s.empty())
                return false;
            for(char c: s)
                if(c<'0' || c>'7')
                    return false;
            return true;
        }

        // Applies one "[ugoa]*[+-=][rwxXst]+" clause to `mode` in place.
        // isDir feeds "X" (execute only if already a directory or any
        // execute bit is already set).
        static bool applyClause(const std::string &clause,mode_t &mode,bool isDir){
            size_t i=0,n=clause.size();
            unsigned who=0;
            while(i<n && (clause[i]=='u' || clause[i]=='g' || clause[i]=='o' || clause[i]=='a')){
                if(clause[i]=='u') who|=04;
                else if(clause[i]=='g') who|=02;
                else if(clause[i]=='o') who|=01;
                else who|=07;
                ++i;
            }
            if(who==0) who=07; // no who-spec: affects all, per umask-less "a"
        bool recursive=false,force=false,verbose=false,changes=false;
        bool haveRef=false;
        mode_t refMode=0,umaskBits=0;

            if(i>=n || (clause[i]!='+' && clause[i]!='-' && clause[i]!='='))
        bool chmodOne(const std::string &spec,const std::string &path,bool top){
            struct stat st;
            if((top ? stat(path.c_str(),&st) : lstat(path.c_str(),&st))!=0){
                if(!force) std::cerr << "chmod: cannot access '" << path << "': " << strerror(errno) << std::endl;
                return false;
            char op=clause[i++];

            bool haveR=false,haveW=false,haveX=false;
            for(; i<n; ++i){
                switch(clause[i]){
                    case 'r': haveR=true; break;
                    case 'w': haveW=true; break;
                    case 'x': haveX=true; break;
                    case 'X':
                        if(isDir || (mode&(S_IXUSR|S_IXGRP|S_IXOTH)))
                            haveX=true;
                        break;
                    case 's': case 't': break; // setuid/gid/sticky: out of scope
                    default: return false;
                }
            }

            mode_t rBit=0,wBit=0,xBit=0;
            if(who&04){ rBit|=S_IRUSR; wBit|=S_IWUSR; xBit|=S_IXUSR; }
            if(who&02){ rBit|=S_IRGRP; wBit|=S_IWGRP; xBit|=S_IXGRP; }
            if(who&01){ rBit|=S_IROTH; wBit|=S_IWOTH; xBit|=S_IXOTH; }

            mode_t want=0;
            if(haveR) want|=rBit;
            if(haveW) want|=wBit;
            if(haveX) want|=xBit;

            mode_t affects=rBit|wBit|xBit;
            if(op=='+') mode|=want;
            else if(op=='-') mode&=~want;
            else mode=(mode&~affects)|want;
            return true;
        }

        static bool applySymbolic(const std::string &spec,mode_t &mode,bool isDir){
            size_t start=0;
            while(start<=spec.size()){
                size_t comma=spec.find(',',start);
                std::string clause=(comma==std::string::npos) ? spec.substr(start) : spec.substr(start,comma-start);
                if(!clause.empty() && !applyClause(clause,mode,isDir))
            if(S_ISLNK(st.st_mode)) return true;
            mode_t old=st.st_mode&07777,nm;
            if(haveRef) nm=refMode;
            else if(!mode::apply(spec,old,S_ISDIR(st.st_mode),umaskBits,nm)){
                std::cerr << "chmod: invalid mode: '" << spec << "'" << std::endl;
                return false;
                if(comma==std::string::npos)
                    break;
                start=comma+1;
            }
            return true;
        }

        // "force": BSD chmod's "-f" -- suppress error messages AND don't
        // let them affect the overall exit status (rpm's %setup runs
        // "chmod -Rf ... buildsubdir" expecting success even when some
        // entries -- e.g. broken symlinks -- can't be chmod'd).
        static bool chmodOne(const std::string &modeSpec,const std::string &path,bool recursive,bool topLevel,bool force){
            struct stat st;
            if(stat(path.c_str(),&st)!=0){
                if(!force)
                    std::cerr << "chmod: " << path << ": " << strerror(errno) << std::endl;
                return force;
            }

            mode_t newMode;
            if(isOctal(modeSpec)){
                newMode=(mode_t)strtol(modeSpec.c_str(),nullptr,8);
            }else{
                newMode=st.st_mode & 07777;
                if(!applySymbolic(modeSpec,newMode,S_ISDIR(st.st_mode))){
                    if(!force)
                        std::cerr << "chmod: invalid mode: " << modeSpec << std::endl;
                    return force;
            }
            }

            bool ok=true;
            if(chmod(path.c_str(),newMode)!=0){
                if(!force)
                    std::cerr << "chmod: " << path << ": " << strerror(errno) << std::endl;
                ok=force;
            if(chmod(path.c_str(),nm)!=0){
                if(!force) std::cerr << "chmod: changing permissions of '" << path << "': " << strerror(errno) << std::endl;
                ok=false;
            }else if(verbose || (changes && nm!=old)){
                auto desc=[](mode_t m){
                    char b[32];
                    std::string r(9,'-');
                    const char *rwx="rwxrwxrwx";
                    for(int k=0;k<9;++k) if(m&(0400>>k)) r[k]=rwx[k];
                    if(m&S_ISUID) r[2]=(m&0100)?'s':'S';
                    if(m&S_ISGID) r[5]=(m&0010)?'s':'S';
                    if(m&S_ISVTX) r[8]=(m&0001)?'t':'T';
                    snprintf(b,sizeof b,"%04o (%s)",(unsigned)m,r.c_str());
                    return std::string(b);
                };
                if(nm!=old) std::cout << "mode of '" << path << "' changed from " << desc(old) << " to " << desc(nm) << std::endl;
                else std::cout << "mode of '" << path << "' retained as " << desc(nm) << std::endl;
            }
            (void)topLevel;

            if(recursive && S_ISDIR(st.st_mode)){
                DIR *d=opendir(path.c_str());
                if(d){
                    struct dirent *ent;
                    while((ent=readdir(d))!=nullptr){
                        std::string name=ent->d_name;
                        if(name=="." || name=="..")
                            continue;
                        std::string child=path;
                        if(!child.empty() && child.back()!='/') child+="/";
                        child+=name;
                        if(!chmodOne(modeSpec,child,true,false,force))
                            ok=false;
                if(!d){
                    if(!force) std::cerr << "chmod: cannot read directory '" << path << "': " << strerror(errno) << std::endl;
                    return false;
                }
                std::vector<std::string> names;
                while(struct dirent *e=readdir(d)){
                    if(!strcmp(e->d_name,".") || !strcmp(e->d_name,"..")) continue;
                    names.push_back(e->d_name);
                }
                closedir(d);
                for(auto &nme: names){
                    std::string child=path;
                    if(child.empty() || child.back()!='/') child+='/';
                    if(!chmodOne(spec,child+nme,false)) ok=false;
                }
            }
            return ok;
        }

        static bool looksLikeMode(const std::string &a){
            if(a.size()<2) return false;
            for(size_t k=1;k<a.size();++k) if(!strchr("rwxXst",a[k])) return false;
            return true;
        }

        int runCommand(int argc,const char **argv){
            bool recursive=false,force=false;
            int i=1;
            for(; i<argc; ++i){
            recursive=force=verbose=changes=haveRef=false;
            umaskBits=mode::currentUmask();
            std::string spec;
            bool haveSpec=false,endOpts=false;
            std::vector<std::string> files;
            for(int i=1;i<argc;++i){
                std::string a=argv[i];
                if(a=="--recursive"){ recursive=true; continue; }
                if(a=="--force" || a=="--silent" || a=="--quiet"){ force=true; continue; }
                // Combined short-option clusters, e.g. rpm's own "-Rf".
                if(a.size()>=2 && a[0]=='-' && a!="--"){
                    bool known=true;
                if(!endOpts && a=="--"){ endOpts=true; continue; }
                if(!endOpts && a.compare(0,2,"--")==0){
                    if(a=="--recursive") recursive=true;
                    else if(a=="--silent" || a=="--quiet") force=true;
                    else if(a=="--verbose") verbose=true;
                    else if(a=="--changes") changes=true;
                    else if(a=="--no-preserve-root" || a=="--preserve-root") {}
                    else if(a.compare(0,12,"--reference=")==0){
                        struct stat rs;
                        if(stat(a.c_str()+12,&rs)!=0){ std::cerr << "chmod: failed to get attributes of '" << a.substr(12) << "': " << strerror(errno) << std::endl; return 1; }
                        refMode=rs.st_mode&07777; haveRef=true;
                    }else{ std::cerr << "chmod: unrecognized option '" << a << "'" << std::endl; return 1; }
                    continue;
                }
                if(!endOpts && a.size()>=2 && a[0]=='-'){
                    bool allOpts=true;
                    for(size_t k=1;k<a.size();++k) if(!strchr("RfvcHLP",a[k])){ allOpts=false; break; }
                    if(allOpts){
                        for(size_t k=1;k<a.size();++k){
                        if(a[k]=='R') recursive=true;
                        else if(a[k]=='f') force=true;
                        else { known=false; break; }
                            if(a[k]=='R') recursive=true; else if(a[k]=='f') force=true;
                            else if(a[k]=='v') verbose=true; else if(a[k]=='c') changes=true;
                        }
                    if(known) continue;
                        continue;
                    }
                    if(!looksLikeMode(a)){ std::cerr << "chmod: invalid option -- '" << a[1] << "'" << std::endl; return 1; }
                }
                break;
                if(!haveSpec && !haveRef){ spec=a; haveSpec=true; continue; }
                files.push_back(a);
            }
            if(i+1>=argc){
                std::cerr << "chmod: usage: chmod [-Rf] MODE FILE..." << std::endl;
            if((!haveSpec && !haveRef) || files.empty()){
                std::cerr << "chmod: missing operand" << std::endl;
                return 1;
            }

            std::string modeSpec=argv[i++];
            if(haveSpec){
                mode_t probe;
                if(!mode::apply(spec,0,false,0,probe)){ std::cerr << "chmod: invalid mode: '" << spec << "'" << std::endl; return 1; }
            }
            bool ok=true;
            for(; i<argc; ++i)
                if(!chmodOne(modeSpec,argv[i],recursive,true,force))
                    ok=false;
            for(auto &f: files) if(!chmodOne(spec,f,true)) ok=false;
            return ok ? 0 : 1;
        }
    };
+767 −0

File added.

Preview size limit exceeded, changes collapsed.

+76 −65
Changes for commands/filesystem/mkdir.cpp: 76 added lines, 65 removed lines.
Original line number Diff line number Diff line
@@ -35,92 +35,103 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <sys/stat.h>

#include "shell.h"
#include "modeutil.h"
#include "../text/textutil.h"

namespace fresh { namespace cmd {

    // mkdir [-p] [-v] [-m MODE] DIR...
    class MkdirCommand : public ShellCommand {
    public:
        MkdirCommand():ShellCommand("mkdir"){}

        static int makeParents(const std::string &path){
            if(path.empty() || path=="/" || path==".")
                return 0;
        bool verbose=false;

        bool makeOne(const std::string &path,mode_t mode,bool parents,bool explicitMode){
            if(mkdir(path.c_str(),mode)!=0){
                int e=errno;
                if(parents){
                    struct stat st;
            if(stat(path.c_str(),&st)==0){
                if(S_ISDIR(st.st_mode))
                    return 0;
                // TEMPORARY diagnostic: stat() has been observed to report
                // success on a path independently confirmed (via a plain
                // host-side ls, outside this process entirely) to be a real
                // directory, yet S_ISDIR() here comes back false -- same
                // shape of bug already found in the self-hosted cmake and
                // llvm-ar. Dump the raw fields to see exactly what this
                // process's own stat() actually returned.
                std::cerr << "mkdir: " << path << ": Not a directory"
                          << " (DEBUG st_mode=0" << std::oct << st.st_mode << std::dec
                          << " sizeof(struct stat)=" << sizeof(st)
                          << " st_dev=" << st.st_dev
                          << " st_ino=" << st.st_ino
                          << " st_nlink=" << st.st_nlink
                          << ")" << std::endl;
                return 1;
                    if(e==EEXIST && stat(path.c_str(),&st)==0 && S_ISDIR(st.st_mode)) return true;
                }

            size_t slash=path.find_last_of('/');
            if(slash!=std::string::npos && slash>0){
                std::string parent=path.substr(0,slash);
                if(makeParents(parent)!=0)
                    return 1;
                std::cerr << "mkdir: cannot create directory '" << path << "': " << strerror(e) << std::endl;
                return false;
            }

            if(mkdir(path.c_str(),0755)!=0 && errno!=EEXIST){
                std::cerr << "mkdir: " << path << ": " << strerror(errno) << std::endl;
                return 1;
            if(verbose) std::cout << "mkdir: created directory '" << path << "'" << std::endl;
            // mkdir(2) filters through the umask, -m must not be
            if(explicitMode && chmod(path.c_str(),mode)!=0){
                std::cerr << "mkdir: cannot set permissions of '" << path << "': " << strerror(errno) << std::endl;
                return false;
            }

            return 0;
            return true;
        }

        int runCommand(int argc,const char **argv){
            optind=0;
            opterr=0;

            bool makeParentDirs=false;
            int opt;
            while((opt=getopt(argc,const_cast<char *const *>(argv),"p"))!=-1){
                switch(opt){
                    case 'p':
                        makeParentDirs=true;
                        break;
                    default:
                        std::cerr << "mkdir: invalid option" << std::endl;
                        return 1;
        bool makeParents(const std::string &path,mode_t mode,bool explicitMode){
            // intermediate directories: u+wx on top of the umask default
            mode_t um=mode::currentUmask();
            mode_t inter=((0777&~um)|S_IWUSR|S_IXUSR);
            std::string cur;
            size_t p=0;
            if(!path.empty() && path[0]=='/'){ cur="/"; p=1; }
            while(p<=path.size()){
                size_t slash=path.find('/',p);
                std::string comp=path.substr(p,slash==std::string::npos ? std::string::npos : slash-p);
                p=slash==std::string::npos ? path.size()+1 : slash+1;
                if(comp.empty()) continue;
                if(!cur.empty() && cur.back()!='/') cur+='/';
                cur+=comp;
                bool last=p>path.size() || path.find_first_not_of('/',p)==std::string::npos;
                if(last) return makeOne(cur,mode,true,explicitMode);
                struct stat st;
                if(stat(cur.c_str(),&st)==0){
                    if(!S_ISDIR(st.st_mode)){
                        std::cerr << "mkdir: cannot create directory '" << path << "': Not a directory" << std::endl;
                        return false;
                    }
                    continue;
                }

            if(optind>=argc){
                std::cerr << "mkdir: missing operand" << std::endl;
                return 1;
                if(mkdir(cur.c_str(),inter)!=0 && errno!=EEXIST){
                    std::cerr << "mkdir: cannot create directory '" << cur << "': " << strerror(errno) << std::endl;
                    return false;
                }

            int failed=0;

            for(int i=optind; i<argc; ++i){
                const char *path=argv[i];

                if(makeParentDirs){
                    if(makeParents(path)!=0)
                        failed=1;
                    continue;
                if(verbose) std::cout << "mkdir: created directory '" << cur << "'" << std::endl;
            }
            return true;
        }

                if(mkdir(path,0755)!=0){
                    std::cerr << "mkdir: " << path << ": " << strerror(errno) << std::endl;
                    failed=1;
        int runCommand(int argc,const char **argv){
            bool parents=false,haveMode=false;
            std::string modeSpec;
            verbose=false;
            text::Opts o=text::parseOpts(argc,argv,"m",[&](char c,const std::string &v)->bool{
                switch(c){
                    case 'p': parents=true; return true;
                    case 'v': verbose=true; return true;
                    case 'm': modeSpec=v; haveMode=true; return true;
                    case 'Z': return true;
                    default: return text::badOpt("mkdir",c);
                }
            },[&](const std::string &n,const std::string &v,bool hasV)->int{
                if(n=="parents"){ parents=true; return 0; }
                if(n=="verbose"){ verbose=true; return 0; }
                if(n=="mode"){ if(!hasV) return 1; modeSpec=v; haveMode=true; return 0; }
                if(n=="context") return 0;
                return text::badLong("mkdir",n);
            });
            if(!o.ok) return 1;
            if(o.operands.empty()){ std::cerr << "mkdir: missing operand" << std::endl; return 1; }
            mode_t um=mode::currentUmask();
            mode_t mode=0777&~um;
            if(haveMode){
                // symbolic modes start from a=rwx
                if(!mode::apply(modeSpec,0777,true,um,mode)){ std::cerr << "mkdir: invalid mode '" << modeSpec << "'" << std::endl; return 1; }
                if(mode::isOctal(modeSpec)) mode=(mode_t)std::stoul(modeSpec,nullptr,8);
            }
            int failed=0;
            for(auto &p: o.operands){
                bool ok=parents ? makeParents(p,haveMode ? mode : 0777,haveMode) : makeOne(p,haveMode ? mode : 0777,false,haveMode);
                if(!ok) failed=1;
            }

            return failed;
        }
    };
+124 −0

File added.

Preview size limit exceeded, changes collapsed.