From 8b95423b5027e3363a3d7fc5e796bc4bf8a0ea15 Mon Sep 17 00:00:00 2001 From: Xavier Defrang Date: Fri, 24 Dec 2021 15:34:30 +0100 Subject: [PATCH] LibCore: Add FilePermissionsMask This class parses UNIX file permissions definitions in numeric (octal) or symbolic (ugoa+rwx) format and can apply them on a given file mode. --- Tests/LibCore/CMakeLists.txt | 1 + .../TestLibCoreFilePermissionsMask.cpp | 83 ++++++++++ Userland/Libraries/LibCore/CMakeLists.txt | 1 + .../Libraries/LibCore/FilePermissionsMask.cpp | 151 ++++++++++++++++++ .../Libraries/LibCore/FilePermissionsMask.h | 40 +++++ 5 files changed, 276 insertions(+) create mode 100644 Tests/LibCore/TestLibCoreFilePermissionsMask.cpp create mode 100644 Userland/Libraries/LibCore/FilePermissionsMask.cpp create mode 100644 Userland/Libraries/LibCore/FilePermissionsMask.h diff --git a/Tests/LibCore/CMakeLists.txt b/Tests/LibCore/CMakeLists.txt index c83c229265..922547c80f 100644 --- a/Tests/LibCore/CMakeLists.txt +++ b/Tests/LibCore/CMakeLists.txt @@ -4,6 +4,7 @@ set(TEST_SOURCES TestLibCoreIODevice.cpp TestLibCoreDeferredInvoke.cpp TestLibCoreStream.cpp + TestLibCoreFilePermissionsMask.cpp ) foreach(source IN LISTS TEST_SOURCES) diff --git a/Tests/LibCore/TestLibCoreFilePermissionsMask.cpp b/Tests/LibCore/TestLibCoreFilePermissionsMask.cpp new file mode 100644 index 0000000000..544bd750a0 --- /dev/null +++ b/Tests/LibCore/TestLibCoreFilePermissionsMask.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2021, Xavier Defrang + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +TEST_CASE(file_permission_mask_from_symbolic_notation) +{ + auto mask = Core::FilePermissionsMask::from_symbolic_notation(""sv); + EXPECT(!mask.is_error()); + EXPECT_EQ(mask.value().clear_mask(), 0); + EXPECT_EQ(mask.value().write_mask(), 0); + EXPECT_EQ(mask.value().apply(0), 0); + EXPECT_EQ(mask.value().apply(0664), 0664); + + mask = Core::FilePermissionsMask::from_symbolic_notation("u+rwx"sv); + EXPECT(!mask.is_error()); + EXPECT_EQ(mask.value().clear_mask(), 0); + EXPECT_EQ(mask.value().write_mask(), 0700); + EXPECT_EQ(mask.value().apply(0), 0700); + EXPECT_EQ(mask.value().apply(0664), 0764); + + mask = Core::FilePermissionsMask::from_symbolic_notation("g+rwx"sv); + EXPECT(!mask.is_error()); + EXPECT_EQ(mask.value().clear_mask(), 0); + EXPECT_EQ(mask.value().write_mask(), 0070); + EXPECT_EQ(mask.value().apply(0), 0070); + EXPECT_EQ(mask.value().apply(0664), 0674); + + mask = Core::FilePermissionsMask::from_symbolic_notation("o+rwx"sv); + EXPECT(!mask.is_error()); + EXPECT_EQ(mask.value().clear_mask(), 0); + EXPECT_EQ(mask.value().write_mask(), 0007); + EXPECT_EQ(mask.value().apply(0), 0007); + EXPECT_EQ(mask.value().apply(0664), 0667); + + mask = Core::FilePermissionsMask::from_symbolic_notation("a=rx"sv); + EXPECT(!mask.is_error()); + EXPECT_EQ(mask.value().clear_mask(), 0777); + EXPECT_EQ(mask.value().write_mask(), 0555); + EXPECT_EQ(mask.value().apply(0), 0555); + EXPECT_EQ(mask.value().apply(0664), 0555); + + mask = Core::FilePermissionsMask::from_symbolic_notation("u+rw,g=rx,o-rwx"sv); + EXPECT(!mask.is_error()); + EXPECT_EQ(mask.value().clear_mask(), 0077); + EXPECT_EQ(mask.value().write_mask(), 0650); + EXPECT_EQ(mask.value().apply(0), 0650); + EXPECT_EQ(mask.value().apply(0177), 0750); + + mask = Core::FilePermissionsMask::from_symbolic_notation("z+rw"sv); + EXPECT(mask.is_error()); + + mask = Core::FilePermissionsMask::from_symbolic_notation("u*rw"sv); + EXPECT(mask.is_error()); + + mask = Core::FilePermissionsMask::from_symbolic_notation("u+rz"sv); + EXPECT(mask.is_error()); + + mask = Core::FilePermissionsMask::from_symbolic_notation("u+rw;g+rw"sv); + EXPECT(mask.is_error()); +} + +TEST_CASE(file_permission_mask_parse) +{ + auto numeric_mask = Core::FilePermissionsMask::parse("750"sv); + auto symbolic_mask = Core::FilePermissionsMask::parse("u=rwx,g=rx,o-rwx"sv); + + EXPECT_EQ(numeric_mask.value().apply(0), 0750); + EXPECT_EQ(symbolic_mask.value().apply(0), 0750); + + EXPECT_EQ(numeric_mask.value().clear_mask(), symbolic_mask.value().clear_mask()); + EXPECT_EQ(numeric_mask.value().write_mask(), symbolic_mask.value().write_mask()); + + auto mask = Core::FilePermissionsMask::parse("888"); + EXPECT(mask.is_error()); + + mask = Core::FilePermissionsMask::parse("z+rw"); + EXPECT(mask.is_error()); +} diff --git a/Userland/Libraries/LibCore/CMakeLists.txt b/Userland/Libraries/LibCore/CMakeLists.txt index db069de046..5981e880fe 100644 --- a/Userland/Libraries/LibCore/CMakeLists.txt +++ b/Userland/Libraries/LibCore/CMakeLists.txt @@ -11,6 +11,7 @@ set(SOURCES EventLoop.cpp FileWatcher.cpp File.cpp + FilePermissionsMask.cpp GetPassword.cpp IODevice.cpp LocalServer.cpp diff --git a/Userland/Libraries/LibCore/FilePermissionsMask.cpp b/Userland/Libraries/LibCore/FilePermissionsMask.cpp new file mode 100644 index 0000000000..1b6cdfc164 --- /dev/null +++ b/Userland/Libraries/LibCore/FilePermissionsMask.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2021, Xavier Defrang + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +#include + +namespace Core { + +enum State { + Reference, + Mode +}; + +enum ClassFlag { + Other = 1, + Group = 2, + User = 4 +}; + +enum Operation { + Add, + Remove, + Assign, +}; + +ErrorOr FilePermissionsMask::parse(StringView string) +{ + return (!string.is_empty() && is_ascii_digit(string[0])) + ? from_numeric_notation(string) + : from_symbolic_notation(string); +} + +ErrorOr FilePermissionsMask::from_numeric_notation(StringView string) +{ + mode_t mode = AK::StringUtils::convert_to_uint_from_octal(string).value_or(01000); + if (mode > 0777) + return Error::from_string_literal("invalid octal representation"sv); + return FilePermissionsMask().assign_permissions(mode); +} + +ErrorOr FilePermissionsMask::from_symbolic_notation(StringView string) +{ + auto mask = FilePermissionsMask(); + + u8 state = State::Reference; + u8 classes = 0; + u8 operation = 0; + + for (auto ch : string) { + switch (state) { + case State::Reference: { + // one or more [ugoa] terminated by one operator [+-=] + if (ch == 'u') + classes |= ClassFlag::User; + else if (ch == 'g') + classes |= ClassFlag::Group; + else if (ch == 'o') + classes |= ClassFlag::Other; + else if (ch == 'a') + classes = ClassFlag::User | ClassFlag::Group | ClassFlag::Other; + else { + if (classes == 0) + return Error::from_string_literal("invalid access class: expected 'u', 'g', 'o' or 'a' "sv); + + if (ch == '+') + operation = Operation::Add; + else if (ch == '-') + operation = Operation::Remove; + else if (ch == '=') + operation = Operation::Assign; + else + return Error::from_string_literal("invalid operation: expected '+', '-' or '='"sv); + + state = State::Mode; + } + + break; + } + + case State::Mode: { + // one or more [rwx] terminated by a comma + + // End of mode part, expect reference next + if (ch == ',') { + state = State::Reference; + classes = operation = 0; + continue; + } + + mode_t write_bits = 0; + + if (ch == 'r') + write_bits = 4; + else if (ch == 'w') + write_bits = 2; + else if (ch == 'x') + write_bits = 1; + else + return Error::from_string_literal("invalid symbolic permission"sv); + + mode_t clear_bits = operation == Operation::Assign ? 7 : write_bits; + + // Update masks one class at a time in other, group, user order + for (auto cls = classes; cls != 0; cls >>= 1) { + if (cls & 1) { + if (operation == Operation::Add || operation == Operation::Assign) + mask.add_permissions(write_bits); + if (operation == Operation::Remove || operation == Operation::Assign) + mask.remove_permissions(clear_bits); + } + write_bits <<= 3; + clear_bits <<= 3; + } + + break; + } + + default: + VERIFY_NOT_REACHED(); + } + } + + return mask; +} + +FilePermissionsMask& FilePermissionsMask::assign_permissions(mode_t mode) +{ + m_write_mask = mode; + m_clear_mask = 0777; + return *this; +} + +FilePermissionsMask& FilePermissionsMask::add_permissions(mode_t mode) +{ + m_write_mask |= mode; + return *this; +} + +FilePermissionsMask& FilePermissionsMask::remove_permissions(mode_t mode) +{ + m_clear_mask |= mode; + return *this; +} + +} diff --git a/Userland/Libraries/LibCore/FilePermissionsMask.h b/Userland/Libraries/LibCore/FilePermissionsMask.h new file mode 100644 index 0000000000..c238344d91 --- /dev/null +++ b/Userland/Libraries/LibCore/FilePermissionsMask.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021, Xavier Defrang + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace Core { + +class FilePermissionsMask { +public: + static ErrorOr parse(StringView string); + static ErrorOr from_numeric_notation(StringView string); + static ErrorOr from_symbolic_notation(StringView string); + + FilePermissionsMask() + : m_clear_mask(0) + , m_write_mask(0) + { + } + + FilePermissionsMask& assign_permissions(mode_t mode); + FilePermissionsMask& add_permissions(mode_t mode); + FilePermissionsMask& remove_permissions(mode_t mode); + + mode_t apply(mode_t mode) const { return m_write_mask | (mode & ~m_clear_mask); } + mode_t clear_mask() const { return m_clear_mask; } + mode_t write_mask() const { return m_write_mask; } + +private: + mode_t m_clear_mask; // the bits that will be cleared + mode_t m_write_mask; // the bits that will be set +}; + +}