diff --git a/Base/usr/share/man/man1/test.md b/Base/usr/share/man/man1/test.md index 4fb6e7eae3..a0925344a4 100644 --- a/Base/usr/share/man/man1/test.md +++ b/Base/usr/share/man/man1/test.md @@ -71,6 +71,11 @@ The expression can take any of the following forms: * `-w ` whether the current user has write access to the file * `-x ` whether the current user has execute access to the file * `-e ` whether the file exists +* `-g ` whether the file exists and has the set-group-ID bit set +* `-G ` whether the file exists and is owned by the effective group ID +* `-k ` whether the file exists and has the sticky bit set +* `-O ` whether the file exists and is owned by the effective user ID +* `-u ` whether the file exists and has the set-user-ID bit set Except for `-h/-L`, all file checks dereference symbolic links. diff --git a/Userland/Utilities/test.cpp b/Userland/Utilities/test.cpp index 6a471dae6b..5f0bd44fe7 100644 --- a/Userland/Utilities/test.cpp +++ b/Userland/Utilities/test.cpp @@ -182,6 +182,89 @@ private: Permission m_kind { Read }; }; +class FileHasFlag : public Condition { +public: + enum Flag { + SGID, + SUID, + SVTX, + }; + FileHasFlag(StringView path, Flag kind) + : m_path(path) + , m_kind(kind) + { + } + +private: + virtual bool check() const override + { + struct stat statbuf; + int rc = stat(m_path.characters(), &statbuf); + + if (rc < 0) { + if (errno != ENOENT) { + perror(m_path.characters()); + g_there_was_an_error = true; + } + return false; + } + + switch (m_kind) { + case SGID: + return statbuf.st_mode & S_ISGID; + case SUID: + return statbuf.st_mode & S_ISUID; + case SVTX: + return statbuf.st_mode & S_ISVTX; + default: + VERIFY_NOT_REACHED(); + } + } + + String m_path; + Flag m_kind { SGID }; +}; + +class FileIsOwnedBy : public Condition { +public: + enum Owner { + EffectiveGID, + EffectiveUID, + }; + FileIsOwnedBy(StringView path, Owner kind) + : m_path(path) + , m_kind(kind) + { + } + +private: + virtual bool check() const override + { + struct stat statbuf; + int rc = stat(m_path.characters(), &statbuf); + + if (rc < 0) { + if (errno != ENOENT) { + perror(m_path.characters()); + g_there_was_an_error = true; + } + return false; + } + + switch (m_kind) { + case EffectiveGID: + return statbuf.st_gid == getgid(); + case EffectiveUID: + return statbuf.st_uid == getuid(); + default: + VERIFY_NOT_REACHED(); + } + } + + String m_path; + Owner m_kind { EffectiveGID }; +}; + class StringCompare : public Condition { public: enum Mode { @@ -374,6 +457,12 @@ static OwnPtr parse_simple_expression(char* argv[]) return make(value, UserHasPermission::Execute); case 'e': return make(value, UserHasPermission::Any); + case 'g': + return make(value, FileHasFlag::SGID); + case 'k': + return make(value, FileHasFlag::SVTX); + case 'u': + return make(value, FileHasFlag::SUID); case 'o': case 'a': // '-a' and '-o' are boolean ops, which are part of a complex expression @@ -384,11 +473,11 @@ static OwnPtr parse_simple_expression(char* argv[]) return make(""sv, value, StringCompare::NotEqual); case 'z': return make(""sv, value, StringCompare::Equal); - case 'g': case 'G': - case 'k': - case 'N': + return make(value, FileIsOwnedBy::EffectiveGID); case 'O': + return make(value, FileIsOwnedBy::EffectiveUID); + case 'N': case 's': // 'optind' has been incremented to refer to the argument after the // operator, while we want to print the operator itself.