Pārlūkot izejas kodu

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.
Xavier Defrang 3 gadi atpakaļ
vecāks
revīzija
8b95423b50

+ 1 - 0
Tests/LibCore/CMakeLists.txt

@@ -4,6 +4,7 @@ set(TEST_SOURCES
     TestLibCoreIODevice.cpp
     TestLibCoreIODevice.cpp
     TestLibCoreDeferredInvoke.cpp
     TestLibCoreDeferredInvoke.cpp
     TestLibCoreStream.cpp
     TestLibCoreStream.cpp
+    TestLibCoreFilePermissionsMask.cpp
 )
 )
 
 
 foreach(source IN LISTS TEST_SOURCES)
 foreach(source IN LISTS TEST_SOURCES)

+ 83 - 0
Tests/LibCore/TestLibCoreFilePermissionsMask.cpp

@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2021, Xavier Defrang <xavier.defrang@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibCore/FilePermissionsMask.h>
+#include <LibTest/TestCase.h>
+
+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());
+}

+ 1 - 0
Userland/Libraries/LibCore/CMakeLists.txt

@@ -11,6 +11,7 @@ set(SOURCES
     EventLoop.cpp
     EventLoop.cpp
     FileWatcher.cpp
     FileWatcher.cpp
     File.cpp
     File.cpp
+    FilePermissionsMask.cpp
     GetPassword.cpp
     GetPassword.cpp
     IODevice.cpp
     IODevice.cpp
     LocalServer.cpp
     LocalServer.cpp

+ 151 - 0
Userland/Libraries/LibCore/FilePermissionsMask.cpp

@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2021, Xavier Defrang <xavier.defrang@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/Assertions.h>
+#include <AK/CharacterTypes.h>
+#include <AK/StringUtils.h>
+
+#include <LibCore/FilePermissionsMask.h>
+
+namespace Core {
+
+enum State {
+    Reference,
+    Mode
+};
+
+enum ClassFlag {
+    Other = 1,
+    Group = 2,
+    User = 4
+};
+
+enum Operation {
+    Add,
+    Remove,
+    Assign,
+};
+
+ErrorOr<FilePermissionsMask> FilePermissionsMask::parse(StringView string)
+{
+    return (!string.is_empty() && is_ascii_digit(string[0]))
+        ? from_numeric_notation(string)
+        : from_symbolic_notation(string);
+}
+
+ErrorOr<FilePermissionsMask> FilePermissionsMask::from_numeric_notation(StringView string)
+{
+    mode_t mode = AK::StringUtils::convert_to_uint_from_octal<u16>(string).value_or(01000);
+    if (mode > 0777)
+        return Error::from_string_literal("invalid octal representation"sv);
+    return FilePermissionsMask().assign_permissions(mode);
+}
+
+ErrorOr<FilePermissionsMask> 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;
+}
+
+}

+ 40 - 0
Userland/Libraries/LibCore/FilePermissionsMask.h

@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2021, Xavier Defrang <xavier.defrang@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/Error.h>
+#include <AK/String.h>
+#include <sys/stat.h>
+
+namespace Core {
+
+class FilePermissionsMask {
+public:
+    static ErrorOr<FilePermissionsMask> parse(StringView string);
+    static ErrorOr<FilePermissionsMask> from_numeric_notation(StringView string);
+    static ErrorOr<FilePermissionsMask> 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
+};
+
+}